@lensjs/fastify 1.0.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 +71 -0
- package/dist/adapter.cjs +236 -0
- package/dist/adapter.d.cts +26 -0
- package/dist/adapter.d.ts +26 -0
- package/dist/adapter.js +207 -0
- package/dist/index.cjs +332 -0
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +310 -0
- package/dist/types.cjs +18 -0
- package/dist/types.d.cts +40 -0
- package/dist/types.d.ts +40 -0
- package/dist/types.js +0 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @lensjs/fastify
|
|
2
|
+
|
|
3
|
+
Fastify adapter for Lens. This package provides middleware and integration points to connect your Fastify 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: FastifyAdapterConfig)` function**: The main entry point to initialize and integrate Lens with a Fastify application.
|
|
8
|
+
* **`FastifyAdapter` class**: Extends `LensAdapter` from `@lensjs/core` to provide Fastify-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 Fastify application.
|
|
13
|
+
* **UI Serving**: Serves the Lens UI within your Fastify 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/fastify
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage Example
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import Fastify from 'fastify';
|
|
28
|
+
import { lens } from '@lensjs/fastify';
|
|
29
|
+
// import { createKyselyHandler } from '@lensjs/watchers/query/kysely'; // Example for Kysely
|
|
30
|
+
|
|
31
|
+
const fastify = Fastify({
|
|
32
|
+
logger: true
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Initialize Lens with Fastify
|
|
36
|
+
lens({
|
|
37
|
+
fastify,
|
|
38
|
+
appName: 'My Fastify App',
|
|
39
|
+
enabled: true, // Set to false in production
|
|
40
|
+
path: '/lens-dashboard', // Access Lens UI at /lens-dashboard
|
|
41
|
+
requestWatcherEnabled: true,
|
|
42
|
+
cacheWatcherEnabled: true,
|
|
43
|
+
exceptionWatcherEnabled: true,
|
|
44
|
+
// queryWatcher: {
|
|
45
|
+
// enabled: true,
|
|
46
|
+
// handler: createKyselyHandler({ provider: 'sqlite' }), // Or createPrismaHandler, createSequelizeHandler
|
|
47
|
+
// },
|
|
48
|
+
// Optional: Integrate with your authentication system
|
|
49
|
+
isAuthenticated: async (req) => {
|
|
50
|
+
return !!req.headers.authorization;
|
|
51
|
+
},
|
|
52
|
+
getUser: async (req) => {
|
|
53
|
+
// Return user details based on your auth system
|
|
54
|
+
return { id: '1', name: 'Authenticated User' };
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Your Fastify routes
|
|
59
|
+
fastify.get('/', async (request, reply) => {
|
|
60
|
+
return { hello: 'world' };
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
fastify.listen({ port: 3000 }, (err, address) => {
|
|
64
|
+
if (err) {
|
|
65
|
+
fastify.log.error(err);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
console.log(`Fastify app listening on ${address}`);
|
|
69
|
+
console.log('Lens UI available at http://localhost:3000/lens-dashboard');
|
|
70
|
+
});
|
|
71
|
+
```
|
package/dist/adapter.cjs
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/adapter.ts
|
|
31
|
+
var adapter_exports = {};
|
|
32
|
+
__export(adapter_exports, {
|
|
33
|
+
FastifyAdapter: () => FastifyAdapter
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(adapter_exports);
|
|
36
|
+
var import_core = require("@lensjs/core");
|
|
37
|
+
var path = __toESM(require("path"), 1);
|
|
38
|
+
var fs = __toESM(require("fs"), 1);
|
|
39
|
+
var import_date = require("@lensjs/date");
|
|
40
|
+
var import_static = __toESM(require("@fastify/static"), 1);
|
|
41
|
+
var FastifyAdapter = class extends import_core.LensAdapter {
|
|
42
|
+
app;
|
|
43
|
+
config;
|
|
44
|
+
constructor({ app }) {
|
|
45
|
+
super();
|
|
46
|
+
this.app = app;
|
|
47
|
+
}
|
|
48
|
+
setConfig(config) {
|
|
49
|
+
this.config = config;
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
setup() {
|
|
53
|
+
for (const watcher of this.getWatchers()) {
|
|
54
|
+
switch (watcher.name) {
|
|
55
|
+
case import_core.WatcherTypeEnum.REQUEST:
|
|
56
|
+
if (this.config.requestWatcherEnabled) {
|
|
57
|
+
this.watchRequests(watcher);
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
case import_core.WatcherTypeEnum.QUERY:
|
|
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
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
registerRoutes(routes) {
|
|
74
|
+
routes.forEach((route) => {
|
|
75
|
+
this.app.route({
|
|
76
|
+
method: route.method.toUpperCase(),
|
|
77
|
+
url: this.normalizePath(route.path),
|
|
78
|
+
handler: async (request, reply) => {
|
|
79
|
+
const result = await route.handler({
|
|
80
|
+
params: request.params,
|
|
81
|
+
qs: request.query
|
|
82
|
+
});
|
|
83
|
+
return reply.send(result);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
serveUI(uiPath, spaRoute, _dataToInject) {
|
|
89
|
+
this.app.register(import_static.default, {
|
|
90
|
+
root: uiPath,
|
|
91
|
+
prefix: `${this.normalizePath(import_core.lensUtils.normalizePath(spaRoute))}/`,
|
|
92
|
+
wildcard: false,
|
|
93
|
+
serve: false
|
|
94
|
+
});
|
|
95
|
+
this.app.get(`/${spaRoute}/*`, async (request, reply) => {
|
|
96
|
+
const url = request.url;
|
|
97
|
+
if (import_core.lensUtils.isStaticFile(url.split("/"))) {
|
|
98
|
+
const filePath = path.join(
|
|
99
|
+
uiPath,
|
|
100
|
+
import_core.lensUtils.stripBeforeAssetsPath(url)
|
|
101
|
+
);
|
|
102
|
+
return reply.sendFile(`${path.relative(uiPath, filePath)}`);
|
|
103
|
+
}
|
|
104
|
+
return reply.sendFile("index.html", uiPath);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async watchCache(watcher) {
|
|
108
|
+
if (!this.config.cacheWatcherEnabled) return;
|
|
109
|
+
import_core.lensEmitter.on("cache", async (data) => {
|
|
110
|
+
await watcher?.log(data);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async watchQueries(watcher) {
|
|
114
|
+
if (!this.config.queryWatcher?.enabled) return;
|
|
115
|
+
const handler = this.config.queryWatcher.handler;
|
|
116
|
+
await handler({
|
|
117
|
+
onQuery: async (query) => {
|
|
118
|
+
const queryPayload = {
|
|
119
|
+
query: query.query,
|
|
120
|
+
duration: query.duration || "0 ms",
|
|
121
|
+
createdAt: query.createdAt || (0, import_date.nowISO)(),
|
|
122
|
+
type: query.type
|
|
123
|
+
};
|
|
124
|
+
await watcher?.log({
|
|
125
|
+
data: queryPayload,
|
|
126
|
+
requestId: import_core.lensContext.getStore()?.requestId ?? ""
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
watchRequests(requestWatcher) {
|
|
132
|
+
if (!this.config.requestWatcherEnabled) return;
|
|
133
|
+
this.app.addHook(
|
|
134
|
+
"onRequest",
|
|
135
|
+
(request, _, done) => {
|
|
136
|
+
if (this.shouldIgnorePath(request.url)) return done();
|
|
137
|
+
const context = {
|
|
138
|
+
requestId: import_core.lensUtils.generateRandomUuid()
|
|
139
|
+
};
|
|
140
|
+
request.lensContext = context;
|
|
141
|
+
request.lensStartTime = process.hrtime();
|
|
142
|
+
import_core.lensContext.run(context, done);
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
this.app.addHook(
|
|
146
|
+
"onSend",
|
|
147
|
+
async (request, reply, payload) => {
|
|
148
|
+
if (this.shouldIgnorePath(request.url)) return payload;
|
|
149
|
+
reply._lensBody = this.parseResponsePayload(payload);
|
|
150
|
+
return payload;
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
this.app.addHook(
|
|
154
|
+
"onResponse",
|
|
155
|
+
async (request, reply) => {
|
|
156
|
+
if (this.shouldIgnorePath(request.url)) return;
|
|
157
|
+
const startTime = request.lensStartTime;
|
|
158
|
+
await this.finalizeRequestLog(
|
|
159
|
+
request,
|
|
160
|
+
reply,
|
|
161
|
+
requestWatcher,
|
|
162
|
+
startTime
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
async finalizeRequestLog(request, reply, requestWatcher, start) {
|
|
168
|
+
try {
|
|
169
|
+
const duration = import_core.lensUtils.prettyHrTime(process.hrtime(start));
|
|
170
|
+
const logPayload = {
|
|
171
|
+
request: {
|
|
172
|
+
id: import_core.lensContext.getStore()?.requestId || import_core.lensUtils.generateRandomUuid(),
|
|
173
|
+
method: request.method,
|
|
174
|
+
duration,
|
|
175
|
+
path: request.url,
|
|
176
|
+
headers: request.headers,
|
|
177
|
+
body: request.body ?? {},
|
|
178
|
+
status: reply.statusCode,
|
|
179
|
+
ip: request.ip ?? "",
|
|
180
|
+
createdAt: (0, import_date.nowISO)()
|
|
181
|
+
},
|
|
182
|
+
response: {
|
|
183
|
+
json: this.parseBody(reply._lensBody),
|
|
184
|
+
headers: reply.getHeaders()
|
|
185
|
+
},
|
|
186
|
+
user: await this.config.isAuthenticated?.(request) ? await this.config.getUser?.(request) : null
|
|
187
|
+
};
|
|
188
|
+
await requestWatcher.log(logPayload);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
console.error("Error finalizing request log:", err);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
parseResponsePayload(payload) {
|
|
194
|
+
if (!payload) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
if (typeof payload === "string") {
|
|
199
|
+
try {
|
|
200
|
+
return JSON.parse(payload);
|
|
201
|
+
} catch {
|
|
202
|
+
const filePath = path.resolve(payload);
|
|
203
|
+
if (fs.existsSync(filePath)) {
|
|
204
|
+
return "Purged By Lens";
|
|
205
|
+
}
|
|
206
|
+
return "Purged By Lens";
|
|
207
|
+
}
|
|
208
|
+
} else if (Buffer.isBuffer(payload)) {
|
|
209
|
+
return "Purged By Lens";
|
|
210
|
+
} else if (typeof payload === "object") {
|
|
211
|
+
return payload;
|
|
212
|
+
} else {
|
|
213
|
+
return payload;
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
return "Purged By Lens";
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
normalizePath(pathStr) {
|
|
220
|
+
return pathStr.startsWith("/") ? pathStr : `/${pathStr}`;
|
|
221
|
+
}
|
|
222
|
+
parseBody(body) {
|
|
223
|
+
if (!body) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
return JSON.parse(body);
|
|
228
|
+
} catch (_e) {
|
|
229
|
+
return body;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
234
|
+
0 && (module.exports = {
|
|
235
|
+
FastifyAdapter
|
|
236
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { LensAdapter, RouteDefinition } from '@lensjs/core';
|
|
2
|
+
import { RequiredFastifyAdapterConfig } from './types.cjs';
|
|
3
|
+
import { FastifyInstance } from 'fastify';
|
|
4
|
+
import '@lensjs/watchers';
|
|
5
|
+
import '@fastify/static';
|
|
6
|
+
|
|
7
|
+
declare class FastifyAdapter extends LensAdapter {
|
|
8
|
+
protected app: FastifyInstance;
|
|
9
|
+
protected config: RequiredFastifyAdapterConfig;
|
|
10
|
+
constructor({ app }: {
|
|
11
|
+
app: FastifyInstance;
|
|
12
|
+
});
|
|
13
|
+
setConfig(config: RequiredFastifyAdapterConfig): this;
|
|
14
|
+
setup(): void;
|
|
15
|
+
registerRoutes(routes: RouteDefinition[]): void;
|
|
16
|
+
serveUI(uiPath: string, spaRoute: string, _dataToInject: Record<string, any>): void;
|
|
17
|
+
private watchCache;
|
|
18
|
+
private watchQueries;
|
|
19
|
+
private watchRequests;
|
|
20
|
+
private finalizeRequestLog;
|
|
21
|
+
private parseResponsePayload;
|
|
22
|
+
private normalizePath;
|
|
23
|
+
private parseBody;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { FastifyAdapter };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { LensAdapter, RouteDefinition } from '@lensjs/core';
|
|
2
|
+
import { RequiredFastifyAdapterConfig } from './types.js';
|
|
3
|
+
import { FastifyInstance } from 'fastify';
|
|
4
|
+
import '@lensjs/watchers';
|
|
5
|
+
import '@fastify/static';
|
|
6
|
+
|
|
7
|
+
declare class FastifyAdapter extends LensAdapter {
|
|
8
|
+
protected app: FastifyInstance;
|
|
9
|
+
protected config: RequiredFastifyAdapterConfig;
|
|
10
|
+
constructor({ app }: {
|
|
11
|
+
app: FastifyInstance;
|
|
12
|
+
});
|
|
13
|
+
setConfig(config: RequiredFastifyAdapterConfig): this;
|
|
14
|
+
setup(): void;
|
|
15
|
+
registerRoutes(routes: RouteDefinition[]): void;
|
|
16
|
+
serveUI(uiPath: string, spaRoute: string, _dataToInject: Record<string, any>): void;
|
|
17
|
+
private watchCache;
|
|
18
|
+
private watchQueries;
|
|
19
|
+
private watchRequests;
|
|
20
|
+
private finalizeRequestLog;
|
|
21
|
+
private parseResponsePayload;
|
|
22
|
+
private normalizePath;
|
|
23
|
+
private parseBody;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { FastifyAdapter };
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// src/adapter.ts
|
|
2
|
+
import {
|
|
3
|
+
LensAdapter,
|
|
4
|
+
lensUtils,
|
|
5
|
+
WatcherTypeEnum,
|
|
6
|
+
lensContext,
|
|
7
|
+
lensEmitter
|
|
8
|
+
} from "@lensjs/core";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
import * as fs from "fs";
|
|
11
|
+
import { nowISO } from "@lensjs/date";
|
|
12
|
+
import fastifyStatic from "@fastify/static";
|
|
13
|
+
var FastifyAdapter = class extends LensAdapter {
|
|
14
|
+
app;
|
|
15
|
+
config;
|
|
16
|
+
constructor({ app }) {
|
|
17
|
+
super();
|
|
18
|
+
this.app = app;
|
|
19
|
+
}
|
|
20
|
+
setConfig(config) {
|
|
21
|
+
this.config = config;
|
|
22
|
+
return this;
|
|
23
|
+
}
|
|
24
|
+
setup() {
|
|
25
|
+
for (const watcher of this.getWatchers()) {
|
|
26
|
+
switch (watcher.name) {
|
|
27
|
+
case WatcherTypeEnum.REQUEST:
|
|
28
|
+
if (this.config.requestWatcherEnabled) {
|
|
29
|
+
this.watchRequests(watcher);
|
|
30
|
+
}
|
|
31
|
+
break;
|
|
32
|
+
case WatcherTypeEnum.QUERY:
|
|
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
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
registerRoutes(routes) {
|
|
46
|
+
routes.forEach((route) => {
|
|
47
|
+
this.app.route({
|
|
48
|
+
method: route.method.toUpperCase(),
|
|
49
|
+
url: this.normalizePath(route.path),
|
|
50
|
+
handler: async (request, reply) => {
|
|
51
|
+
const result = await route.handler({
|
|
52
|
+
params: request.params,
|
|
53
|
+
qs: request.query
|
|
54
|
+
});
|
|
55
|
+
return reply.send(result);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
serveUI(uiPath, spaRoute, _dataToInject) {
|
|
61
|
+
this.app.register(fastifyStatic, {
|
|
62
|
+
root: uiPath,
|
|
63
|
+
prefix: `${this.normalizePath(lensUtils.normalizePath(spaRoute))}/`,
|
|
64
|
+
wildcard: false,
|
|
65
|
+
serve: false
|
|
66
|
+
});
|
|
67
|
+
this.app.get(`/${spaRoute}/*`, async (request, reply) => {
|
|
68
|
+
const url = request.url;
|
|
69
|
+
if (lensUtils.isStaticFile(url.split("/"))) {
|
|
70
|
+
const filePath = path.join(
|
|
71
|
+
uiPath,
|
|
72
|
+
lensUtils.stripBeforeAssetsPath(url)
|
|
73
|
+
);
|
|
74
|
+
return reply.sendFile(`${path.relative(uiPath, filePath)}`);
|
|
75
|
+
}
|
|
76
|
+
return reply.sendFile("index.html", uiPath);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async watchCache(watcher) {
|
|
80
|
+
if (!this.config.cacheWatcherEnabled) return;
|
|
81
|
+
lensEmitter.on("cache", async (data) => {
|
|
82
|
+
await watcher?.log(data);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async watchQueries(watcher) {
|
|
86
|
+
if (!this.config.queryWatcher?.enabled) return;
|
|
87
|
+
const handler = this.config.queryWatcher.handler;
|
|
88
|
+
await handler({
|
|
89
|
+
onQuery: async (query) => {
|
|
90
|
+
const queryPayload = {
|
|
91
|
+
query: query.query,
|
|
92
|
+
duration: query.duration || "0 ms",
|
|
93
|
+
createdAt: query.createdAt || nowISO(),
|
|
94
|
+
type: query.type
|
|
95
|
+
};
|
|
96
|
+
await watcher?.log({
|
|
97
|
+
data: queryPayload,
|
|
98
|
+
requestId: lensContext.getStore()?.requestId ?? ""
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
watchRequests(requestWatcher) {
|
|
104
|
+
if (!this.config.requestWatcherEnabled) return;
|
|
105
|
+
this.app.addHook(
|
|
106
|
+
"onRequest",
|
|
107
|
+
(request, _, done) => {
|
|
108
|
+
if (this.shouldIgnorePath(request.url)) return done();
|
|
109
|
+
const context = {
|
|
110
|
+
requestId: lensUtils.generateRandomUuid()
|
|
111
|
+
};
|
|
112
|
+
request.lensContext = context;
|
|
113
|
+
request.lensStartTime = process.hrtime();
|
|
114
|
+
lensContext.run(context, done);
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
this.app.addHook(
|
|
118
|
+
"onSend",
|
|
119
|
+
async (request, reply, payload) => {
|
|
120
|
+
if (this.shouldIgnorePath(request.url)) return payload;
|
|
121
|
+
reply._lensBody = this.parseResponsePayload(payload);
|
|
122
|
+
return payload;
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
this.app.addHook(
|
|
126
|
+
"onResponse",
|
|
127
|
+
async (request, reply) => {
|
|
128
|
+
if (this.shouldIgnorePath(request.url)) return;
|
|
129
|
+
const startTime = request.lensStartTime;
|
|
130
|
+
await this.finalizeRequestLog(
|
|
131
|
+
request,
|
|
132
|
+
reply,
|
|
133
|
+
requestWatcher,
|
|
134
|
+
startTime
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
async finalizeRequestLog(request, reply, requestWatcher, start) {
|
|
140
|
+
try {
|
|
141
|
+
const duration = lensUtils.prettyHrTime(process.hrtime(start));
|
|
142
|
+
const logPayload = {
|
|
143
|
+
request: {
|
|
144
|
+
id: lensContext.getStore()?.requestId || lensUtils.generateRandomUuid(),
|
|
145
|
+
method: request.method,
|
|
146
|
+
duration,
|
|
147
|
+
path: request.url,
|
|
148
|
+
headers: request.headers,
|
|
149
|
+
body: request.body ?? {},
|
|
150
|
+
status: reply.statusCode,
|
|
151
|
+
ip: request.ip ?? "",
|
|
152
|
+
createdAt: nowISO()
|
|
153
|
+
},
|
|
154
|
+
response: {
|
|
155
|
+
json: this.parseBody(reply._lensBody),
|
|
156
|
+
headers: reply.getHeaders()
|
|
157
|
+
},
|
|
158
|
+
user: await this.config.isAuthenticated?.(request) ? await this.config.getUser?.(request) : null
|
|
159
|
+
};
|
|
160
|
+
await requestWatcher.log(logPayload);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.error("Error finalizing request log:", err);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
parseResponsePayload(payload) {
|
|
166
|
+
if (!payload) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
if (typeof payload === "string") {
|
|
171
|
+
try {
|
|
172
|
+
return JSON.parse(payload);
|
|
173
|
+
} catch {
|
|
174
|
+
const filePath = path.resolve(payload);
|
|
175
|
+
if (fs.existsSync(filePath)) {
|
|
176
|
+
return "Purged By Lens";
|
|
177
|
+
}
|
|
178
|
+
return "Purged By Lens";
|
|
179
|
+
}
|
|
180
|
+
} else if (Buffer.isBuffer(payload)) {
|
|
181
|
+
return "Purged By Lens";
|
|
182
|
+
} else if (typeof payload === "object") {
|
|
183
|
+
return payload;
|
|
184
|
+
} else {
|
|
185
|
+
return payload;
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
return "Purged By Lens";
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
normalizePath(pathStr) {
|
|
192
|
+
return pathStr.startsWith("/") ? pathStr : `/${pathStr}`;
|
|
193
|
+
}
|
|
194
|
+
parseBody(body) {
|
|
195
|
+
if (!body) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
return JSON.parse(body);
|
|
200
|
+
} catch (_e) {
|
|
201
|
+
return body;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
export {
|
|
206
|
+
FastifyAdapter
|
|
207
|
+
};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
FastifyAdapter: () => FastifyAdapter,
|
|
34
|
+
lens: () => lens
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
var import_core2 = require("@lensjs/core");
|
|
38
|
+
|
|
39
|
+
// src/adapter.ts
|
|
40
|
+
var import_core = require("@lensjs/core");
|
|
41
|
+
var path = __toESM(require("path"), 1);
|
|
42
|
+
var fs = __toESM(require("fs"), 1);
|
|
43
|
+
var import_date = require("@lensjs/date");
|
|
44
|
+
var import_static = __toESM(require("@fastify/static"), 1);
|
|
45
|
+
var FastifyAdapter = class extends import_core.LensAdapter {
|
|
46
|
+
app;
|
|
47
|
+
config;
|
|
48
|
+
constructor({ app }) {
|
|
49
|
+
super();
|
|
50
|
+
this.app = app;
|
|
51
|
+
}
|
|
52
|
+
setConfig(config) {
|
|
53
|
+
this.config = config;
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
setup() {
|
|
57
|
+
for (const watcher of this.getWatchers()) {
|
|
58
|
+
switch (watcher.name) {
|
|
59
|
+
case import_core.WatcherTypeEnum.REQUEST:
|
|
60
|
+
if (this.config.requestWatcherEnabled) {
|
|
61
|
+
this.watchRequests(watcher);
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
case import_core.WatcherTypeEnum.QUERY:
|
|
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
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
registerRoutes(routes) {
|
|
78
|
+
routes.forEach((route) => {
|
|
79
|
+
this.app.route({
|
|
80
|
+
method: route.method.toUpperCase(),
|
|
81
|
+
url: this.normalizePath(route.path),
|
|
82
|
+
handler: async (request, reply) => {
|
|
83
|
+
const result = await route.handler({
|
|
84
|
+
params: request.params,
|
|
85
|
+
qs: request.query
|
|
86
|
+
});
|
|
87
|
+
return reply.send(result);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
serveUI(uiPath, spaRoute, _dataToInject) {
|
|
93
|
+
this.app.register(import_static.default, {
|
|
94
|
+
root: uiPath,
|
|
95
|
+
prefix: `${this.normalizePath(import_core.lensUtils.normalizePath(spaRoute))}/`,
|
|
96
|
+
wildcard: false,
|
|
97
|
+
serve: false
|
|
98
|
+
});
|
|
99
|
+
this.app.get(`/${spaRoute}/*`, async (request, reply) => {
|
|
100
|
+
const url = request.url;
|
|
101
|
+
if (import_core.lensUtils.isStaticFile(url.split("/"))) {
|
|
102
|
+
const filePath = path.join(
|
|
103
|
+
uiPath,
|
|
104
|
+
import_core.lensUtils.stripBeforeAssetsPath(url)
|
|
105
|
+
);
|
|
106
|
+
return reply.sendFile(`${path.relative(uiPath, filePath)}`);
|
|
107
|
+
}
|
|
108
|
+
return reply.sendFile("index.html", uiPath);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
async watchCache(watcher) {
|
|
112
|
+
if (!this.config.cacheWatcherEnabled) return;
|
|
113
|
+
import_core.lensEmitter.on("cache", async (data) => {
|
|
114
|
+
await watcher?.log(data);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async watchQueries(watcher) {
|
|
118
|
+
if (!this.config.queryWatcher?.enabled) return;
|
|
119
|
+
const handler = this.config.queryWatcher.handler;
|
|
120
|
+
await handler({
|
|
121
|
+
onQuery: async (query) => {
|
|
122
|
+
const queryPayload = {
|
|
123
|
+
query: query.query,
|
|
124
|
+
duration: query.duration || "0 ms",
|
|
125
|
+
createdAt: query.createdAt || (0, import_date.nowISO)(),
|
|
126
|
+
type: query.type
|
|
127
|
+
};
|
|
128
|
+
await watcher?.log({
|
|
129
|
+
data: queryPayload,
|
|
130
|
+
requestId: import_core.lensContext.getStore()?.requestId ?? ""
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
watchRequests(requestWatcher) {
|
|
136
|
+
if (!this.config.requestWatcherEnabled) return;
|
|
137
|
+
this.app.addHook(
|
|
138
|
+
"onRequest",
|
|
139
|
+
(request, _, done) => {
|
|
140
|
+
if (this.shouldIgnorePath(request.url)) return done();
|
|
141
|
+
const context = {
|
|
142
|
+
requestId: import_core.lensUtils.generateRandomUuid()
|
|
143
|
+
};
|
|
144
|
+
request.lensContext = context;
|
|
145
|
+
request.lensStartTime = process.hrtime();
|
|
146
|
+
import_core.lensContext.run(context, done);
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
this.app.addHook(
|
|
150
|
+
"onSend",
|
|
151
|
+
async (request, reply, payload) => {
|
|
152
|
+
if (this.shouldIgnorePath(request.url)) return payload;
|
|
153
|
+
reply._lensBody = this.parseResponsePayload(payload);
|
|
154
|
+
return payload;
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
this.app.addHook(
|
|
158
|
+
"onResponse",
|
|
159
|
+
async (request, reply) => {
|
|
160
|
+
if (this.shouldIgnorePath(request.url)) return;
|
|
161
|
+
const startTime = request.lensStartTime;
|
|
162
|
+
await this.finalizeRequestLog(
|
|
163
|
+
request,
|
|
164
|
+
reply,
|
|
165
|
+
requestWatcher,
|
|
166
|
+
startTime
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
async finalizeRequestLog(request, reply, requestWatcher, start) {
|
|
172
|
+
try {
|
|
173
|
+
const duration = import_core.lensUtils.prettyHrTime(process.hrtime(start));
|
|
174
|
+
const logPayload = {
|
|
175
|
+
request: {
|
|
176
|
+
id: import_core.lensContext.getStore()?.requestId || import_core.lensUtils.generateRandomUuid(),
|
|
177
|
+
method: request.method,
|
|
178
|
+
duration,
|
|
179
|
+
path: request.url,
|
|
180
|
+
headers: request.headers,
|
|
181
|
+
body: request.body ?? {},
|
|
182
|
+
status: reply.statusCode,
|
|
183
|
+
ip: request.ip ?? "",
|
|
184
|
+
createdAt: (0, import_date.nowISO)()
|
|
185
|
+
},
|
|
186
|
+
response: {
|
|
187
|
+
json: this.parseBody(reply._lensBody),
|
|
188
|
+
headers: reply.getHeaders()
|
|
189
|
+
},
|
|
190
|
+
user: await this.config.isAuthenticated?.(request) ? await this.config.getUser?.(request) : null
|
|
191
|
+
};
|
|
192
|
+
await requestWatcher.log(logPayload);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
console.error("Error finalizing request log:", err);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
parseResponsePayload(payload) {
|
|
198
|
+
if (!payload) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
if (typeof payload === "string") {
|
|
203
|
+
try {
|
|
204
|
+
return JSON.parse(payload);
|
|
205
|
+
} catch {
|
|
206
|
+
const filePath = path.resolve(payload);
|
|
207
|
+
if (fs.existsSync(filePath)) {
|
|
208
|
+
return "Purged By Lens";
|
|
209
|
+
}
|
|
210
|
+
return "Purged By Lens";
|
|
211
|
+
}
|
|
212
|
+
} else if (Buffer.isBuffer(payload)) {
|
|
213
|
+
return "Purged By Lens";
|
|
214
|
+
} else if (typeof payload === "object") {
|
|
215
|
+
return payload;
|
|
216
|
+
} else {
|
|
217
|
+
return payload;
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
return "Purged By Lens";
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
normalizePath(pathStr) {
|
|
224
|
+
return pathStr.startsWith("/") ? pathStr : `/${pathStr}`;
|
|
225
|
+
}
|
|
226
|
+
parseBody(body) {
|
|
227
|
+
if (!body) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
return JSON.parse(body);
|
|
232
|
+
} catch (_e) {
|
|
233
|
+
return body;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// src/index.ts
|
|
239
|
+
var import_core3 = require("@lensjs/core");
|
|
240
|
+
var import_core4 = require("@lensjs/core");
|
|
241
|
+
var defaultConfig = {
|
|
242
|
+
appName: "Lens",
|
|
243
|
+
enabled: true,
|
|
244
|
+
path: "/lens",
|
|
245
|
+
ignoredPaths: [],
|
|
246
|
+
onlyPaths: [],
|
|
247
|
+
requestWatcherEnabled: true,
|
|
248
|
+
cacheWatcherEnabled: false,
|
|
249
|
+
exceptionWatcherEnabled: true,
|
|
250
|
+
registerErrorHandler: true
|
|
251
|
+
};
|
|
252
|
+
var lens = async (config) => {
|
|
253
|
+
const adapter = new FastifyAdapter({ app: config.app });
|
|
254
|
+
const watchers = [];
|
|
255
|
+
const mergedConfig = {
|
|
256
|
+
...defaultConfig,
|
|
257
|
+
...config
|
|
258
|
+
};
|
|
259
|
+
const defaultWatchers = [
|
|
260
|
+
{
|
|
261
|
+
enabled: mergedConfig.requestWatcherEnabled,
|
|
262
|
+
watcher: new import_core2.RequestWatcher()
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
enabled: mergedConfig.cacheWatcherEnabled,
|
|
266
|
+
watcher: new import_core2.CacheWatcher()
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
enabled: mergedConfig.queryWatcher?.enabled,
|
|
270
|
+
watcher: new import_core2.QueryWatcher()
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
enabled: mergedConfig.exceptionWatcherEnabled,
|
|
274
|
+
watcher: new import_core2.ExceptionWatcher()
|
|
275
|
+
}
|
|
276
|
+
];
|
|
277
|
+
defaultWatchers.forEach((watcher) => {
|
|
278
|
+
if (watcher.enabled) {
|
|
279
|
+
watchers.push(watcher.watcher);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
const { ignoredPaths, normalizedPath } = import_core2.lensUtils.prepareIgnoredPaths(
|
|
283
|
+
mergedConfig.path,
|
|
284
|
+
mergedConfig.ignoredPaths
|
|
285
|
+
);
|
|
286
|
+
adapter.setConfig(mergedConfig).setIgnoredPaths(ignoredPaths).setOnlyPaths(mergedConfig.onlyPaths);
|
|
287
|
+
if (mergedConfig.exceptionWatcherEnabled && mergedConfig.registerErrorHandler) {
|
|
288
|
+
handleExceptions({
|
|
289
|
+
app: mergedConfig.app,
|
|
290
|
+
enabled: mergedConfig.exceptionWatcherEnabled && mergedConfig.enabled,
|
|
291
|
+
watcher: watchers.find(
|
|
292
|
+
(w) => w.name === import_core3.WatcherTypeEnum.EXCEPTION
|
|
293
|
+
)
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
await import_core2.Lens.setAdapter(adapter).setWatchers(watchers).start({
|
|
297
|
+
appName: mergedConfig.appName,
|
|
298
|
+
enabled: mergedConfig.enabled,
|
|
299
|
+
basePath: normalizedPath
|
|
300
|
+
});
|
|
301
|
+
const exceptionWatcher = watchers.find(
|
|
302
|
+
(w) => w.name === import_core3.WatcherTypeEnum.EXCEPTION
|
|
303
|
+
);
|
|
304
|
+
return {
|
|
305
|
+
logException: (error) => logException(
|
|
306
|
+
error,
|
|
307
|
+
mergedConfig.exceptionWatcherEnabled && mergedConfig.enabled,
|
|
308
|
+
exceptionWatcher
|
|
309
|
+
)
|
|
310
|
+
};
|
|
311
|
+
};
|
|
312
|
+
function logException(error, enabled, watcher) {
|
|
313
|
+
if (!enabled || !watcher) return;
|
|
314
|
+
watcher.log({
|
|
315
|
+
...import_core4.lensExceptionUtils.constructErrorObject(error),
|
|
316
|
+
requestId: import_core2.lensContext.getStore()?.requestId ?? ""
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
function handleExceptions({
|
|
320
|
+
app,
|
|
321
|
+
enabled,
|
|
322
|
+
watcher
|
|
323
|
+
}) {
|
|
324
|
+
app.setErrorHandler((err) => {
|
|
325
|
+
logException(err, enabled, watcher);
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
329
|
+
0 && (module.exports = {
|
|
330
|
+
FastifyAdapter,
|
|
331
|
+
lens
|
|
332
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FastifyAdapterConfig } from './types.cjs';
|
|
2
|
+
export { RequiredFastifyAdapterConfig } from './types.cjs';
|
|
3
|
+
import { FastifyError } from 'fastify';
|
|
4
|
+
export { FastifyAdapter } from './adapter.cjs';
|
|
5
|
+
import '@lensjs/watchers';
|
|
6
|
+
import '@lensjs/core';
|
|
7
|
+
import '@fastify/static';
|
|
8
|
+
|
|
9
|
+
declare const lens: (config: FastifyAdapterConfig) => Promise<{
|
|
10
|
+
logException: (error: FastifyError) => void;
|
|
11
|
+
}>;
|
|
12
|
+
|
|
13
|
+
export { FastifyAdapterConfig, lens };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FastifyAdapterConfig } from './types.js';
|
|
2
|
+
export { RequiredFastifyAdapterConfig } from './types.js';
|
|
3
|
+
import { FastifyError } from 'fastify';
|
|
4
|
+
export { FastifyAdapter } from './adapter.js';
|
|
5
|
+
import '@lensjs/watchers';
|
|
6
|
+
import '@lensjs/core';
|
|
7
|
+
import '@fastify/static';
|
|
8
|
+
|
|
9
|
+
declare const lens: (config: FastifyAdapterConfig) => Promise<{
|
|
10
|
+
logException: (error: FastifyError) => void;
|
|
11
|
+
}>;
|
|
12
|
+
|
|
13
|
+
export { FastifyAdapterConfig, lens };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
CacheWatcher as CacheWatcher2,
|
|
4
|
+
ExceptionWatcher,
|
|
5
|
+
Lens,
|
|
6
|
+
lensContext as lensContext2,
|
|
7
|
+
lensUtils as lensUtils2,
|
|
8
|
+
QueryWatcher as QueryWatcher2,
|
|
9
|
+
RequestWatcher as RequestWatcher2
|
|
10
|
+
} from "@lensjs/core";
|
|
11
|
+
|
|
12
|
+
// src/adapter.ts
|
|
13
|
+
import {
|
|
14
|
+
LensAdapter,
|
|
15
|
+
lensUtils,
|
|
16
|
+
WatcherTypeEnum,
|
|
17
|
+
lensContext,
|
|
18
|
+
lensEmitter
|
|
19
|
+
} from "@lensjs/core";
|
|
20
|
+
import * as path from "path";
|
|
21
|
+
import * as fs from "fs";
|
|
22
|
+
import { nowISO } from "@lensjs/date";
|
|
23
|
+
import fastifyStatic from "@fastify/static";
|
|
24
|
+
var FastifyAdapter = class extends LensAdapter {
|
|
25
|
+
app;
|
|
26
|
+
config;
|
|
27
|
+
constructor({ app }) {
|
|
28
|
+
super();
|
|
29
|
+
this.app = app;
|
|
30
|
+
}
|
|
31
|
+
setConfig(config) {
|
|
32
|
+
this.config = config;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
setup() {
|
|
36
|
+
for (const watcher of this.getWatchers()) {
|
|
37
|
+
switch (watcher.name) {
|
|
38
|
+
case WatcherTypeEnum.REQUEST:
|
|
39
|
+
if (this.config.requestWatcherEnabled) {
|
|
40
|
+
this.watchRequests(watcher);
|
|
41
|
+
}
|
|
42
|
+
break;
|
|
43
|
+
case WatcherTypeEnum.QUERY:
|
|
44
|
+
if (this.config.queryWatcher?.enabled) {
|
|
45
|
+
void this.watchQueries(watcher);
|
|
46
|
+
}
|
|
47
|
+
break;
|
|
48
|
+
case WatcherTypeEnum.CACHE:
|
|
49
|
+
if (this.config.cacheWatcherEnabled) {
|
|
50
|
+
void this.watchCache(watcher);
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
registerRoutes(routes) {
|
|
57
|
+
routes.forEach((route) => {
|
|
58
|
+
this.app.route({
|
|
59
|
+
method: route.method.toUpperCase(),
|
|
60
|
+
url: this.normalizePath(route.path),
|
|
61
|
+
handler: async (request, reply) => {
|
|
62
|
+
const result = await route.handler({
|
|
63
|
+
params: request.params,
|
|
64
|
+
qs: request.query
|
|
65
|
+
});
|
|
66
|
+
return reply.send(result);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
serveUI(uiPath, spaRoute, _dataToInject) {
|
|
72
|
+
this.app.register(fastifyStatic, {
|
|
73
|
+
root: uiPath,
|
|
74
|
+
prefix: `${this.normalizePath(lensUtils.normalizePath(spaRoute))}/`,
|
|
75
|
+
wildcard: false,
|
|
76
|
+
serve: false
|
|
77
|
+
});
|
|
78
|
+
this.app.get(`/${spaRoute}/*`, async (request, reply) => {
|
|
79
|
+
const url = request.url;
|
|
80
|
+
if (lensUtils.isStaticFile(url.split("/"))) {
|
|
81
|
+
const filePath = path.join(
|
|
82
|
+
uiPath,
|
|
83
|
+
lensUtils.stripBeforeAssetsPath(url)
|
|
84
|
+
);
|
|
85
|
+
return reply.sendFile(`${path.relative(uiPath, filePath)}`);
|
|
86
|
+
}
|
|
87
|
+
return reply.sendFile("index.html", uiPath);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async watchCache(watcher) {
|
|
91
|
+
if (!this.config.cacheWatcherEnabled) return;
|
|
92
|
+
lensEmitter.on("cache", async (data) => {
|
|
93
|
+
await watcher?.log(data);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async watchQueries(watcher) {
|
|
97
|
+
if (!this.config.queryWatcher?.enabled) return;
|
|
98
|
+
const handler = this.config.queryWatcher.handler;
|
|
99
|
+
await handler({
|
|
100
|
+
onQuery: async (query) => {
|
|
101
|
+
const queryPayload = {
|
|
102
|
+
query: query.query,
|
|
103
|
+
duration: query.duration || "0 ms",
|
|
104
|
+
createdAt: query.createdAt || nowISO(),
|
|
105
|
+
type: query.type
|
|
106
|
+
};
|
|
107
|
+
await watcher?.log({
|
|
108
|
+
data: queryPayload,
|
|
109
|
+
requestId: lensContext.getStore()?.requestId ?? ""
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
watchRequests(requestWatcher) {
|
|
115
|
+
if (!this.config.requestWatcherEnabled) return;
|
|
116
|
+
this.app.addHook(
|
|
117
|
+
"onRequest",
|
|
118
|
+
(request, _, done) => {
|
|
119
|
+
if (this.shouldIgnorePath(request.url)) return done();
|
|
120
|
+
const context = {
|
|
121
|
+
requestId: lensUtils.generateRandomUuid()
|
|
122
|
+
};
|
|
123
|
+
request.lensContext = context;
|
|
124
|
+
request.lensStartTime = process.hrtime();
|
|
125
|
+
lensContext.run(context, done);
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
this.app.addHook(
|
|
129
|
+
"onSend",
|
|
130
|
+
async (request, reply, payload) => {
|
|
131
|
+
if (this.shouldIgnorePath(request.url)) return payload;
|
|
132
|
+
reply._lensBody = this.parseResponsePayload(payload);
|
|
133
|
+
return payload;
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
this.app.addHook(
|
|
137
|
+
"onResponse",
|
|
138
|
+
async (request, reply) => {
|
|
139
|
+
if (this.shouldIgnorePath(request.url)) return;
|
|
140
|
+
const startTime = request.lensStartTime;
|
|
141
|
+
await this.finalizeRequestLog(
|
|
142
|
+
request,
|
|
143
|
+
reply,
|
|
144
|
+
requestWatcher,
|
|
145
|
+
startTime
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
async finalizeRequestLog(request, reply, requestWatcher, start) {
|
|
151
|
+
try {
|
|
152
|
+
const duration = lensUtils.prettyHrTime(process.hrtime(start));
|
|
153
|
+
const logPayload = {
|
|
154
|
+
request: {
|
|
155
|
+
id: lensContext.getStore()?.requestId || lensUtils.generateRandomUuid(),
|
|
156
|
+
method: request.method,
|
|
157
|
+
duration,
|
|
158
|
+
path: request.url,
|
|
159
|
+
headers: request.headers,
|
|
160
|
+
body: request.body ?? {},
|
|
161
|
+
status: reply.statusCode,
|
|
162
|
+
ip: request.ip ?? "",
|
|
163
|
+
createdAt: nowISO()
|
|
164
|
+
},
|
|
165
|
+
response: {
|
|
166
|
+
json: this.parseBody(reply._lensBody),
|
|
167
|
+
headers: reply.getHeaders()
|
|
168
|
+
},
|
|
169
|
+
user: await this.config.isAuthenticated?.(request) ? await this.config.getUser?.(request) : null
|
|
170
|
+
};
|
|
171
|
+
await requestWatcher.log(logPayload);
|
|
172
|
+
} catch (err) {
|
|
173
|
+
console.error("Error finalizing request log:", err);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
parseResponsePayload(payload) {
|
|
177
|
+
if (!payload) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
if (typeof payload === "string") {
|
|
182
|
+
try {
|
|
183
|
+
return JSON.parse(payload);
|
|
184
|
+
} catch {
|
|
185
|
+
const filePath = path.resolve(payload);
|
|
186
|
+
if (fs.existsSync(filePath)) {
|
|
187
|
+
return "Purged By Lens";
|
|
188
|
+
}
|
|
189
|
+
return "Purged By Lens";
|
|
190
|
+
}
|
|
191
|
+
} else if (Buffer.isBuffer(payload)) {
|
|
192
|
+
return "Purged By Lens";
|
|
193
|
+
} else if (typeof payload === "object") {
|
|
194
|
+
return payload;
|
|
195
|
+
} else {
|
|
196
|
+
return payload;
|
|
197
|
+
}
|
|
198
|
+
} catch {
|
|
199
|
+
return "Purged By Lens";
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
normalizePath(pathStr) {
|
|
203
|
+
return pathStr.startsWith("/") ? pathStr : `/${pathStr}`;
|
|
204
|
+
}
|
|
205
|
+
parseBody(body) {
|
|
206
|
+
if (!body) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
return JSON.parse(body);
|
|
211
|
+
} catch (_e) {
|
|
212
|
+
return body;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// src/index.ts
|
|
218
|
+
import { WatcherTypeEnum as WatcherTypeEnum2 } from "@lensjs/core";
|
|
219
|
+
import { lensExceptionUtils } from "@lensjs/core";
|
|
220
|
+
var defaultConfig = {
|
|
221
|
+
appName: "Lens",
|
|
222
|
+
enabled: true,
|
|
223
|
+
path: "/lens",
|
|
224
|
+
ignoredPaths: [],
|
|
225
|
+
onlyPaths: [],
|
|
226
|
+
requestWatcherEnabled: true,
|
|
227
|
+
cacheWatcherEnabled: false,
|
|
228
|
+
exceptionWatcherEnabled: true,
|
|
229
|
+
registerErrorHandler: true
|
|
230
|
+
};
|
|
231
|
+
var lens = async (config) => {
|
|
232
|
+
const adapter = new FastifyAdapter({ app: config.app });
|
|
233
|
+
const watchers = [];
|
|
234
|
+
const mergedConfig = {
|
|
235
|
+
...defaultConfig,
|
|
236
|
+
...config
|
|
237
|
+
};
|
|
238
|
+
const defaultWatchers = [
|
|
239
|
+
{
|
|
240
|
+
enabled: mergedConfig.requestWatcherEnabled,
|
|
241
|
+
watcher: new RequestWatcher2()
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
enabled: mergedConfig.cacheWatcherEnabled,
|
|
245
|
+
watcher: new CacheWatcher2()
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
enabled: mergedConfig.queryWatcher?.enabled,
|
|
249
|
+
watcher: new QueryWatcher2()
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
enabled: mergedConfig.exceptionWatcherEnabled,
|
|
253
|
+
watcher: new ExceptionWatcher()
|
|
254
|
+
}
|
|
255
|
+
];
|
|
256
|
+
defaultWatchers.forEach((watcher) => {
|
|
257
|
+
if (watcher.enabled) {
|
|
258
|
+
watchers.push(watcher.watcher);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
const { ignoredPaths, normalizedPath } = lensUtils2.prepareIgnoredPaths(
|
|
262
|
+
mergedConfig.path,
|
|
263
|
+
mergedConfig.ignoredPaths
|
|
264
|
+
);
|
|
265
|
+
adapter.setConfig(mergedConfig).setIgnoredPaths(ignoredPaths).setOnlyPaths(mergedConfig.onlyPaths);
|
|
266
|
+
if (mergedConfig.exceptionWatcherEnabled && mergedConfig.registerErrorHandler) {
|
|
267
|
+
handleExceptions({
|
|
268
|
+
app: mergedConfig.app,
|
|
269
|
+
enabled: mergedConfig.exceptionWatcherEnabled && mergedConfig.enabled,
|
|
270
|
+
watcher: watchers.find(
|
|
271
|
+
(w) => w.name === WatcherTypeEnum2.EXCEPTION
|
|
272
|
+
)
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
await Lens.setAdapter(adapter).setWatchers(watchers).start({
|
|
276
|
+
appName: mergedConfig.appName,
|
|
277
|
+
enabled: mergedConfig.enabled,
|
|
278
|
+
basePath: normalizedPath
|
|
279
|
+
});
|
|
280
|
+
const exceptionWatcher = watchers.find(
|
|
281
|
+
(w) => w.name === WatcherTypeEnum2.EXCEPTION
|
|
282
|
+
);
|
|
283
|
+
return {
|
|
284
|
+
logException: (error) => logException(
|
|
285
|
+
error,
|
|
286
|
+
mergedConfig.exceptionWatcherEnabled && mergedConfig.enabled,
|
|
287
|
+
exceptionWatcher
|
|
288
|
+
)
|
|
289
|
+
};
|
|
290
|
+
};
|
|
291
|
+
function logException(error, enabled, watcher) {
|
|
292
|
+
if (!enabled || !watcher) return;
|
|
293
|
+
watcher.log({
|
|
294
|
+
...lensExceptionUtils.constructErrorObject(error),
|
|
295
|
+
requestId: lensContext2.getStore()?.requestId ?? ""
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
function handleExceptions({
|
|
299
|
+
app,
|
|
300
|
+
enabled,
|
|
301
|
+
watcher
|
|
302
|
+
}) {
|
|
303
|
+
app.setErrorHandler((err) => {
|
|
304
|
+
logException(err, enabled, watcher);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
export {
|
|
308
|
+
FastifyAdapter,
|
|
309
|
+
lens
|
|
310
|
+
};
|
package/dist/types.cjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
|
|
16
|
+
// src/types.ts
|
|
17
|
+
var types_exports = {};
|
|
18
|
+
module.exports = __toCommonJS(types_exports);
|
package/dist/types.d.cts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { FastifyInstance, FastifyRequest } from 'fastify';
|
|
2
|
+
import { QueryWatcherHandler } from '@lensjs/watchers';
|
|
3
|
+
import { UserEntry } from '@lensjs/core';
|
|
4
|
+
import { SendOptions } from '@fastify/static';
|
|
5
|
+
|
|
6
|
+
type FastifyAdapterConfig = {
|
|
7
|
+
app: FastifyInstance;
|
|
8
|
+
appName?: string;
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
path?: string;
|
|
11
|
+
ignoredPaths?: RegExp[];
|
|
12
|
+
onlyPaths?: RegExp[];
|
|
13
|
+
requestWatcherEnabled?: boolean;
|
|
14
|
+
cacheWatcherEnabled?: boolean;
|
|
15
|
+
exceptionWatcherEnabled?: boolean;
|
|
16
|
+
registerErrorHandler?: boolean;
|
|
17
|
+
queryWatcher?: {
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
handler: QueryWatcherHandler;
|
|
20
|
+
};
|
|
21
|
+
isAuthenticated?: (request: FastifyRequest) => Promise<boolean>;
|
|
22
|
+
getUser?: (request: FastifyRequest) => Promise<UserEntry>;
|
|
23
|
+
};
|
|
24
|
+
type RequiredFastifyAdapterConfig = Required<FastifyAdapterConfig> & {
|
|
25
|
+
queryWatcher?: FastifyAdapterConfig["queryWatcher"];
|
|
26
|
+
isAuthenticated?: FastifyAdapterConfig["isAuthenticated"];
|
|
27
|
+
getUser?: FastifyAdapterConfig["getUser"];
|
|
28
|
+
};
|
|
29
|
+
declare module "fastify" {
|
|
30
|
+
interface FastifyReply {
|
|
31
|
+
sendFile(filename: string, rootPath?: string): FastifyReply;
|
|
32
|
+
sendFile(filename: string, options?: SendOptions): FastifyReply;
|
|
33
|
+
sendFile(filename: string, rootPath?: string, options?: SendOptions): FastifyReply;
|
|
34
|
+
download(filepath: string, options?: SendOptions): FastifyReply;
|
|
35
|
+
download(filepath: string, filename?: string): FastifyReply;
|
|
36
|
+
download(filepath: string, filename?: string, options?: SendOptions): FastifyReply;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type { FastifyAdapterConfig, RequiredFastifyAdapterConfig };
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { FastifyInstance, FastifyRequest } from 'fastify';
|
|
2
|
+
import { QueryWatcherHandler } from '@lensjs/watchers';
|
|
3
|
+
import { UserEntry } from '@lensjs/core';
|
|
4
|
+
import { SendOptions } from '@fastify/static';
|
|
5
|
+
|
|
6
|
+
type FastifyAdapterConfig = {
|
|
7
|
+
app: FastifyInstance;
|
|
8
|
+
appName?: string;
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
path?: string;
|
|
11
|
+
ignoredPaths?: RegExp[];
|
|
12
|
+
onlyPaths?: RegExp[];
|
|
13
|
+
requestWatcherEnabled?: boolean;
|
|
14
|
+
cacheWatcherEnabled?: boolean;
|
|
15
|
+
exceptionWatcherEnabled?: boolean;
|
|
16
|
+
registerErrorHandler?: boolean;
|
|
17
|
+
queryWatcher?: {
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
handler: QueryWatcherHandler;
|
|
20
|
+
};
|
|
21
|
+
isAuthenticated?: (request: FastifyRequest) => Promise<boolean>;
|
|
22
|
+
getUser?: (request: FastifyRequest) => Promise<UserEntry>;
|
|
23
|
+
};
|
|
24
|
+
type RequiredFastifyAdapterConfig = Required<FastifyAdapterConfig> & {
|
|
25
|
+
queryWatcher?: FastifyAdapterConfig["queryWatcher"];
|
|
26
|
+
isAuthenticated?: FastifyAdapterConfig["isAuthenticated"];
|
|
27
|
+
getUser?: FastifyAdapterConfig["getUser"];
|
|
28
|
+
};
|
|
29
|
+
declare module "fastify" {
|
|
30
|
+
interface FastifyReply {
|
|
31
|
+
sendFile(filename: string, rootPath?: string): FastifyReply;
|
|
32
|
+
sendFile(filename: string, options?: SendOptions): FastifyReply;
|
|
33
|
+
sendFile(filename: string, rootPath?: string, options?: SendOptions): FastifyReply;
|
|
34
|
+
download(filepath: string, options?: SendOptions): FastifyReply;
|
|
35
|
+
download(filepath: string, filename?: string): FastifyReply;
|
|
36
|
+
download(filepath: string, filename?: string, options?: SendOptions): FastifyReply;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type { FastifyAdapterConfig, RequiredFastifyAdapterConfig };
|
package/dist/types.js
ADDED
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lensjs/fastify",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Fastify adapter for LensJs",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"require": "./dist/index.cjs",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@fastify/static": "8.2.0",
|
|
22
|
+
"@lensjs/core": "2.2.0",
|
|
23
|
+
"@lensjs/date": "1.0.12",
|
|
24
|
+
"@lensjs/typescript-config": "1.0.12",
|
|
25
|
+
"@lensjs/watchers": "1.0.16"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"fastify": "^5.6.0",
|
|
29
|
+
"vitest": "^3.2.4"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"fastify": "^5.2.1"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsup --clean",
|
|
36
|
+
"test": "vitest run"
|
|
37
|
+
}
|
|
38
|
+
}
|