@tracelet/express 0.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 +23 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +22 -0
- package/dist/jsdoc/index.d.ts +1 -0
- package/dist/jsdoc/index.js +7 -0
- package/dist/jsdoc/parseRouteJSDoc.d.ts +17 -0
- package/dist/jsdoc/parseRouteJSDoc.js +432 -0
- package/dist/middleware.d.ts +7 -0
- package/dist/middleware.js +46 -0
- package/dist/routeDiscovery.d.ts +9 -0
- package/dist/routeDiscovery.js +36 -0
- package/dist/traceletDoc/handler/auth.d.ts +4 -0
- package/dist/traceletDoc/handler/auth.js +51 -0
- package/dist/traceletDoc/handlers/auth.d.ts +2 -0
- package/dist/traceletDoc/handlers/auth.js +22 -0
- package/dist/traceletDoc/index.d.ts +12 -0
- package/dist/traceletDoc/index.js +90 -0
- package/dist/traceletDoc/routeDiscovery.d.ts +33 -0
- package/dist/traceletDoc/routeDiscovery.js +104 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.js +5 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Tracelet (Express)
|
|
2
|
+
|
|
3
|
+
Lightweight request logging for Express apps.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
yarn add @tracelet/express
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
```ts
|
|
10
|
+
import express from "express";
|
|
11
|
+
import tracelet from "@tracelet/express";
|
|
12
|
+
|
|
13
|
+
const app = express();
|
|
14
|
+
|
|
15
|
+
app.use(tracelet({
|
|
16
|
+
serviceName: "example-api"
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
app.get("/users/:id", (req, res) => {
|
|
20
|
+
res.json({ id: req.params.id });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
app.listen(3000);
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.tracelet = void 0;
|
|
18
|
+
__exportStar(require("./middleware"), exports);
|
|
19
|
+
__exportStar(require("./types"), exports);
|
|
20
|
+
var middleware_1 = require("./middleware");
|
|
21
|
+
Object.defineProperty(exports, "tracelet", { enumerable: true, get: function () { return middleware_1.traceletMiddleware; } });
|
|
22
|
+
__exportStar(require("./traceletDoc/index"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { parseFileForRouteJSDoc, parseRouteFiles, buildRouteTree, } from "./parseRouteJSDoc";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildRouteTree = exports.parseRouteFiles = exports.parseFileForRouteJSDoc = void 0;
|
|
4
|
+
var parseRouteJSDoc_1 = require("./parseRouteJSDoc");
|
|
5
|
+
Object.defineProperty(exports, "parseFileForRouteJSDoc", { enumerable: true, get: function () { return parseRouteJSDoc_1.parseFileForRouteJSDoc; } });
|
|
6
|
+
Object.defineProperty(exports, "parseRouteFiles", { enumerable: true, get: function () { return parseRouteJSDoc_1.parseRouteFiles; } });
|
|
7
|
+
Object.defineProperty(exports, "buildRouteTree", { enumerable: true, get: function () { return parseRouteJSDoc_1.buildRouteTree; } });
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TraceletMeta } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Parse a single file and extract route registrations with their preceding JSDoc.
|
|
4
|
+
* Uses format: @description, @request { type }, @response [...], @query { type }.
|
|
5
|
+
* Path params are extracted from the route path.
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseFileForRouteJSDoc(filePath: string): TraceletMeta[];
|
|
8
|
+
/**
|
|
9
|
+
* Parse multiple files and return TraceletMeta[] for each route found.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseRouteFiles(filePaths: string[]): TraceletMeta[];
|
|
12
|
+
/**
|
|
13
|
+
* Build a parent-child route tree with PARENT grouping nodes.
|
|
14
|
+
* - If routes are /user, /user/:id, /user/:id/posts → parent is /user (method PARENT), children are those three with their methods.
|
|
15
|
+
* - If routes are only /user/:id, /user/:id/posts → parent is /user/:id (method PARENT), children are those two with their methods.
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildRouteTree(flat: TraceletMeta[]): TraceletMeta[];
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.parseFileForRouteJSDoc = parseFileForRouteJSDoc;
|
|
18
|
+
exports.parseRouteFiles = parseRouteFiles;
|
|
19
|
+
exports.buildRouteTree = buildRouteTree;
|
|
20
|
+
var fs_1 = __importDefault(require("fs"));
|
|
21
|
+
var path_1 = __importDefault(require("path"));
|
|
22
|
+
var ROUTE_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
|
|
23
|
+
var ROUTE_REG = /(?:app|router)\.(get|post|put|patch|delete|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/gi;
|
|
24
|
+
/** Match JSDoc block comment - non-greedy. */
|
|
25
|
+
var BLOCK_COMMENT_REG = /\/\*\*([\s\S]*?)\*\//g;
|
|
26
|
+
/** Normalize type string to a simple type name. */
|
|
27
|
+
function normalizeType(t) {
|
|
28
|
+
var s = (t || "string").trim().toLowerCase();
|
|
29
|
+
if (s.includes("number"))
|
|
30
|
+
return "number";
|
|
31
|
+
if (s.includes("boolean"))
|
|
32
|
+
return "boolean";
|
|
33
|
+
if (s.includes("object") || s.includes("{}"))
|
|
34
|
+
return "object";
|
|
35
|
+
if (s.includes("array") || s.includes("[]"))
|
|
36
|
+
return "array";
|
|
37
|
+
return "string";
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Parse a type expression inside braces to extract properties.
|
|
41
|
+
* e.g. " name: string, age: number " or " limit: number, offset: number "
|
|
42
|
+
* If no "key: type" pairs (e.g. just "updateUserRequest"), returns empty or single placeholder.
|
|
43
|
+
*/
|
|
44
|
+
function parseTypeProperties(inner) {
|
|
45
|
+
var trimmed = inner.replace(/\s+/g, " ").trim();
|
|
46
|
+
if (!trimmed)
|
|
47
|
+
return [];
|
|
48
|
+
var pairReg = /(\w+)\s*:\s*([^,}]+)/g;
|
|
49
|
+
var props = [];
|
|
50
|
+
var m;
|
|
51
|
+
while ((m = pairReg.exec(trimmed)) !== null) {
|
|
52
|
+
props.push({
|
|
53
|
+
name: m[1].trim(),
|
|
54
|
+
type: normalizeType(m[2].trim()),
|
|
55
|
+
required: false,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (props.length === 0 && trimmed) {
|
|
59
|
+
props.push({ name: "body", type: "object", desc: trimmed });
|
|
60
|
+
}
|
|
61
|
+
return props;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Extract a single top-level { ... } (balanced braces) starting at position.
|
|
65
|
+
*/
|
|
66
|
+
function extractBracedObject(str, start) {
|
|
67
|
+
if (str[start] !== "{")
|
|
68
|
+
return null;
|
|
69
|
+
var depth = 0;
|
|
70
|
+
for (var i = start; i < str.length; i++) {
|
|
71
|
+
if (str[i] === "{")
|
|
72
|
+
depth++;
|
|
73
|
+
else if (str[i] === "}") {
|
|
74
|
+
depth--;
|
|
75
|
+
if (depth === 0)
|
|
76
|
+
return str.slice(start, i + 1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Parse properties from object string like { id: "string", name: "string" }.
|
|
83
|
+
*/
|
|
84
|
+
function parsePropertiesObject(objStr) {
|
|
85
|
+
var inner = objStr.replace(/^\s*\{\s*|\s*\}\s*$/g, "").trim();
|
|
86
|
+
if (!inner)
|
|
87
|
+
return [];
|
|
88
|
+
var props = [];
|
|
89
|
+
var pairReg = /(\w+)\s*:\s*["']?([^,"'}]+)["']?/g;
|
|
90
|
+
var m;
|
|
91
|
+
while ((m = pairReg.exec(inner)) !== null) {
|
|
92
|
+
props.push({
|
|
93
|
+
name: m[1].trim(),
|
|
94
|
+
type: normalizeType(m[2].trim()),
|
|
95
|
+
required: false,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return props;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Strip optional @type from the start of content, then return rest (for highlighting; parsing ignores it).
|
|
102
|
+
*/
|
|
103
|
+
function stripOptionalType(content) {
|
|
104
|
+
return content.trim().replace(/^@type\s*/i, "").trim();
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Parse @response format. @type is optional (for highlighting).
|
|
108
|
+
* @response @type { status: 200, description: "Success", properties: { id: string, name: string } }
|
|
109
|
+
* or @response { status: 200, description: "Success", properties: { id: string } }
|
|
110
|
+
*/
|
|
111
|
+
function parseResponseTag(block) {
|
|
112
|
+
var _a;
|
|
113
|
+
var responses = [];
|
|
114
|
+
var responseReg = /@response\s+/gi;
|
|
115
|
+
var match;
|
|
116
|
+
while ((match = responseReg.exec(block)) !== null) {
|
|
117
|
+
var after = stripOptionalType(block.slice(match.index + match[0].length));
|
|
118
|
+
var open_1 = after.indexOf("{");
|
|
119
|
+
if (open_1 === -1)
|
|
120
|
+
continue;
|
|
121
|
+
var objStr = extractBracedObject(after, open_1);
|
|
122
|
+
if (!objStr)
|
|
123
|
+
continue;
|
|
124
|
+
var statusMatch = objStr.match(/status\s*:\s*(\d+)/i);
|
|
125
|
+
var descMatch = (_a = objStr.match(/(?:description|descrition)\s*:\s*["']([^"']*)["']/i)) !== null && _a !== void 0 ? _a : objStr.match(/(?:description|descrition)\s*:\s*(\S+)/i);
|
|
126
|
+
var status_1 = statusMatch ? parseInt(statusMatch[1], 10) : 200;
|
|
127
|
+
var description = descMatch ? (descMatch[1].trim().replace(/^["']|["']$/g, "") || undefined) : undefined;
|
|
128
|
+
var properties = [];
|
|
129
|
+
var propsKeyIdx = objStr.search(/properties\s*:\s*/i);
|
|
130
|
+
if (propsKeyIdx !== -1) {
|
|
131
|
+
var afterProps = stripOptionalType(objStr.slice(propsKeyIdx).replace(/properties\s*:\s*/i, ""));
|
|
132
|
+
if (afterProps.startsWith("{")) {
|
|
133
|
+
var innerObj = extractBracedObject(afterProps, 0);
|
|
134
|
+
if (innerObj)
|
|
135
|
+
properties = parsePropertiesObject(innerObj);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
responses.push({ status: status_1, description: description, properties: properties });
|
|
139
|
+
}
|
|
140
|
+
return responses;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* From a string like "@type { a: b }" or "{ a: b }", extract the braced content and parse as type properties.
|
|
144
|
+
*/
|
|
145
|
+
function parseTypeFromLineOrBlock(content) {
|
|
146
|
+
var trimmed = content.trim();
|
|
147
|
+
var typeStart = trimmed.search(/@type\s*/i);
|
|
148
|
+
var from = typeStart >= 0 ? trimmed.slice(typeStart).replace(/^@type\s*/i, "").trim() : trimmed;
|
|
149
|
+
var open = from.indexOf("{");
|
|
150
|
+
if (open === -1)
|
|
151
|
+
return [];
|
|
152
|
+
var obj = extractBracedObject(from, open);
|
|
153
|
+
return obj ? parseTypeProperties(obj.slice(1, -1)) : [];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Parse JSDoc block. @type is optional everywhere (for highlighting).
|
|
157
|
+
* @description - endpoint description
|
|
158
|
+
* @request @type { ... } or @request { ... }
|
|
159
|
+
* @response @type { status: 200, description: "Success", properties: { id: string } } or @response { ... }
|
|
160
|
+
* @query @type { limit: number, offset: number } or @query { ... }
|
|
161
|
+
* @params @type { id: string } or @params { ... } — path params; if omitted, derived from route path.
|
|
162
|
+
*/
|
|
163
|
+
function parseJSDocBlock(block, routePath) {
|
|
164
|
+
var lines = block.split(/\r?\n/).map(function (l) { return l.replace(/^\s*\*\s?/, "").trim(); });
|
|
165
|
+
var request;
|
|
166
|
+
var query;
|
|
167
|
+
var paramsFromJSDoc;
|
|
168
|
+
var descriptionLines = [];
|
|
169
|
+
var currentTag = null;
|
|
170
|
+
var seenAnyTag = false;
|
|
171
|
+
for (var i = 0; i < lines.length; i++) {
|
|
172
|
+
var line = lines[i];
|
|
173
|
+
var tagMatch = line.match(/^@(description|request|response|query|params)\s*(.*)/i);
|
|
174
|
+
if (tagMatch) {
|
|
175
|
+
seenAnyTag = true;
|
|
176
|
+
currentTag = tagMatch[1].toLowerCase();
|
|
177
|
+
if (currentTag === "description") {
|
|
178
|
+
if (tagMatch[2].trim())
|
|
179
|
+
descriptionLines.push(tagMatch[2].trim());
|
|
180
|
+
}
|
|
181
|
+
else if (currentTag === "request") {
|
|
182
|
+
var parsed = parseTypeFromLineOrBlock(tagMatch[2]);
|
|
183
|
+
if (parsed.length)
|
|
184
|
+
request = parsed;
|
|
185
|
+
}
|
|
186
|
+
else if (currentTag === "query") {
|
|
187
|
+
var parsed = parseTypeFromLineOrBlock(tagMatch[2]);
|
|
188
|
+
if (parsed.length)
|
|
189
|
+
query = parsed;
|
|
190
|
+
}
|
|
191
|
+
else if (currentTag === "params") {
|
|
192
|
+
var parsed = parseTypeFromLineOrBlock(tagMatch[2]);
|
|
193
|
+
if (parsed.length)
|
|
194
|
+
paramsFromJSDoc = parsed;
|
|
195
|
+
}
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (currentTag === "description" && line.length > 0) {
|
|
199
|
+
descriptionLines.push(line);
|
|
200
|
+
}
|
|
201
|
+
else if (currentTag === "request" && line.length > 0 && !line.startsWith("@")) {
|
|
202
|
+
var parsed = parseTypeFromLineOrBlock(line);
|
|
203
|
+
if (parsed.length)
|
|
204
|
+
request = parsed;
|
|
205
|
+
}
|
|
206
|
+
else if (currentTag === "query" && line.length > 0 && !line.startsWith("@")) {
|
|
207
|
+
var parsed = parseTypeFromLineOrBlock(line);
|
|
208
|
+
if (parsed.length)
|
|
209
|
+
query = parsed;
|
|
210
|
+
}
|
|
211
|
+
else if (currentTag === "params" && line.length > 0 && !line.startsWith("@")) {
|
|
212
|
+
var parsed = parseTypeFromLineOrBlock(line);
|
|
213
|
+
if (parsed.length)
|
|
214
|
+
paramsFromJSDoc = parsed;
|
|
215
|
+
}
|
|
216
|
+
else if (!seenAnyTag && line.length > 0) {
|
|
217
|
+
descriptionLines.push(line);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
var description = descriptionLines.length > 0 ? descriptionLines.join(" ").trim() : undefined;
|
|
221
|
+
var responses = parseResponseTag(block);
|
|
222
|
+
var params = (paramsFromJSDoc === null || paramsFromJSDoc === void 0 ? void 0 : paramsFromJSDoc.length) ? paramsFromJSDoc : extractParamsFromPath(routePath);
|
|
223
|
+
return {
|
|
224
|
+
description: description,
|
|
225
|
+
request: (request === null || request === void 0 ? void 0 : request.length) ? request : undefined,
|
|
226
|
+
query: (query === null || query === void 0 ? void 0 : query.length) ? query : undefined,
|
|
227
|
+
params: params.length ? params : undefined,
|
|
228
|
+
responses: responses.length ? responses : undefined,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/** Extract path param names from route path (e.g. /users/:id/posts/:postId → id, postId). */
|
|
232
|
+
function extractParamsFromPath(routePath) {
|
|
233
|
+
var params = [];
|
|
234
|
+
var paramReg = /:(\w+)/g;
|
|
235
|
+
var m;
|
|
236
|
+
while ((m = paramReg.exec(routePath)) !== null) {
|
|
237
|
+
params.push({
|
|
238
|
+
name: m[1],
|
|
239
|
+
type: "string",
|
|
240
|
+
required: true,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
return params;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Find the block comment that ends immediately before position (closest above).
|
|
247
|
+
*/
|
|
248
|
+
function getCommentBefore(content, position) {
|
|
249
|
+
var blocks = [];
|
|
250
|
+
var match;
|
|
251
|
+
BLOCK_COMMENT_REG.lastIndex = 0;
|
|
252
|
+
while ((match = BLOCK_COMMENT_REG.exec(content)) !== null) {
|
|
253
|
+
var end = match.index + match[0].length;
|
|
254
|
+
if (end <= position) {
|
|
255
|
+
blocks.push({ end: end, body: match[1] });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (blocks.length === 0)
|
|
259
|
+
return null;
|
|
260
|
+
var closest = blocks.reduce(function (a, b) { return (b.end > a.end ? b : a); });
|
|
261
|
+
return closest.body;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Parse a single file and extract route registrations with their preceding JSDoc.
|
|
265
|
+
* Uses format: @description, @request { type }, @response [...], @query { type }.
|
|
266
|
+
* Path params are extracted from the route path.
|
|
267
|
+
*/
|
|
268
|
+
function parseFileForRouteJSDoc(filePath) {
|
|
269
|
+
var _a, _b, _c, _d;
|
|
270
|
+
var absolutePath = path_1.default.resolve(filePath);
|
|
271
|
+
var content;
|
|
272
|
+
try {
|
|
273
|
+
content = fs_1.default.readFileSync(absolutePath, "utf-8");
|
|
274
|
+
}
|
|
275
|
+
catch (_e) {
|
|
276
|
+
return [];
|
|
277
|
+
}
|
|
278
|
+
var results = [];
|
|
279
|
+
var match;
|
|
280
|
+
ROUTE_REG.lastIndex = 0;
|
|
281
|
+
while ((match = ROUTE_REG.exec(content)) !== null) {
|
|
282
|
+
var method = match[1].toUpperCase();
|
|
283
|
+
var routePath = match[2].trim();
|
|
284
|
+
var fullPath = routePath.startsWith("/") ? routePath : "/".concat(routePath);
|
|
285
|
+
if (!ROUTE_METHODS.includes(match[1].toLowerCase()))
|
|
286
|
+
continue;
|
|
287
|
+
var comment = getCommentBefore(content, match.index);
|
|
288
|
+
var meta = {
|
|
289
|
+
method: method,
|
|
290
|
+
path: fullPath,
|
|
291
|
+
};
|
|
292
|
+
if (comment) {
|
|
293
|
+
var parsed = parseJSDocBlock(comment, fullPath);
|
|
294
|
+
if (parsed.description)
|
|
295
|
+
meta.description = parsed.description;
|
|
296
|
+
if ((_a = parsed.request) === null || _a === void 0 ? void 0 : _a.length)
|
|
297
|
+
meta.request = parsed.request;
|
|
298
|
+
if ((_b = parsed.query) === null || _b === void 0 ? void 0 : _b.length)
|
|
299
|
+
meta.query = parsed.query;
|
|
300
|
+
if ((_c = parsed.params) === null || _c === void 0 ? void 0 : _c.length)
|
|
301
|
+
meta.params = parsed.params;
|
|
302
|
+
if ((_d = parsed.responses) === null || _d === void 0 ? void 0 : _d.length)
|
|
303
|
+
meta.responses = parsed.responses;
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
var pathParams = extractParamsFromPath(fullPath);
|
|
307
|
+
meta.params = pathParams.length ? pathParams : undefined;
|
|
308
|
+
}
|
|
309
|
+
results.push(meta);
|
|
310
|
+
}
|
|
311
|
+
return results;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Parse multiple files and return TraceletMeta[] for each route found.
|
|
315
|
+
*/
|
|
316
|
+
function parseRouteFiles(filePaths) {
|
|
317
|
+
var seen = new Set();
|
|
318
|
+
var out = [];
|
|
319
|
+
var _loop_1 = function (fp) {
|
|
320
|
+
var resolved = path_1.default.resolve(fp);
|
|
321
|
+
var stat = fs_1.default.existsSync(resolved) ? fs_1.default.statSync(resolved) : null;
|
|
322
|
+
if (stat === null || stat === void 0 ? void 0 : stat.isDirectory()) {
|
|
323
|
+
var entries = fs_1.default.readdirSync(resolved, { withFileTypes: true });
|
|
324
|
+
var files = entries
|
|
325
|
+
.filter(function (e) { return e.isFile() && /\.(js|ts|mjs|cjs)$/.test(e.name); })
|
|
326
|
+
.map(function (e) { return path_1.default.join(resolved, e.name); });
|
|
327
|
+
for (var _a = 0, files_1 = files; _a < files_1.length; _a++) {
|
|
328
|
+
var f = files_1[_a];
|
|
329
|
+
var metas = parseFileForRouteJSDoc(f);
|
|
330
|
+
for (var _b = 0, metas_1 = metas; _b < metas_1.length; _b++) {
|
|
331
|
+
var m = metas_1[_b];
|
|
332
|
+
var key = "".concat(m.method, ":").concat(m.path);
|
|
333
|
+
if (!seen.has(key)) {
|
|
334
|
+
seen.add(key);
|
|
335
|
+
out.push(m);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
var metas = parseFileForRouteJSDoc(resolved);
|
|
342
|
+
for (var _c = 0, metas_2 = metas; _c < metas_2.length; _c++) {
|
|
343
|
+
var m = metas_2[_c];
|
|
344
|
+
var key = "".concat(m.method, ":").concat(m.path);
|
|
345
|
+
if (!seen.has(key)) {
|
|
346
|
+
seen.add(key);
|
|
347
|
+
out.push(m);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
for (var _i = 0, filePaths_1 = filePaths; _i < filePaths_1.length; _i++) {
|
|
353
|
+
var fp = filePaths_1[_i];
|
|
354
|
+
_loop_1(fp);
|
|
355
|
+
}
|
|
356
|
+
return out;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Build a parent-child route tree with PARENT grouping nodes.
|
|
360
|
+
* - If routes are /user, /user/:id, /user/:id/posts → parent is /user (method PARENT), children are those three with their methods.
|
|
361
|
+
* - If routes are only /user/:id, /user/:id/posts → parent is /user/:id (method PARENT), children are those two with their methods.
|
|
362
|
+
*/
|
|
363
|
+
function buildRouteTree(flat) {
|
|
364
|
+
var _a;
|
|
365
|
+
var normalized = flat.map(function (r) { return (__assign(__assign({}, r), { path: (r.path.startsWith("/") ? r.path : "/".concat(r.path)).replace(/\/+/g, "/") || "/" })); });
|
|
366
|
+
var pathSet = new Set(normalized.map(function (r) { return r.path; }));
|
|
367
|
+
var pathList = Array.from(pathSet);
|
|
368
|
+
/** Longest path in pathSet that is a strict prefix of path, or null. */
|
|
369
|
+
function groupParent(path) {
|
|
370
|
+
var best = null;
|
|
371
|
+
for (var _i = 0, pathList_1 = pathList; _i < pathList_1.length; _i++) {
|
|
372
|
+
var p = pathList_1[_i];
|
|
373
|
+
if (path !== p && path.startsWith(p + "/") && (best === null || p.length > best.length)) {
|
|
374
|
+
best = p;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return best;
|
|
378
|
+
}
|
|
379
|
+
var groups = new Map();
|
|
380
|
+
for (var _i = 0, normalized_1 = normalized; _i < normalized_1.length; _i++) {
|
|
381
|
+
var r = normalized_1[_i];
|
|
382
|
+
var p = groupParent(r.path);
|
|
383
|
+
if (p !== null) {
|
|
384
|
+
var list = (_a = groups.get(p)) !== null && _a !== void 0 ? _a : [];
|
|
385
|
+
list.push(r);
|
|
386
|
+
groups.set(p, list);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
var roots = normalized.filter(function (r) { return groupParent(r.path) === null; });
|
|
390
|
+
function toRelativePath(fullPath, parentPath) {
|
|
391
|
+
var rel = fullPath.slice(parentPath.length).replace(/^\//, "") || "";
|
|
392
|
+
return rel ? "/".concat(rel) : "/";
|
|
393
|
+
}
|
|
394
|
+
function buildGroupNode(parentPath, routeMeta, childrenPaths) {
|
|
395
|
+
var childNodes = [];
|
|
396
|
+
if (routeMeta) {
|
|
397
|
+
childNodes.push(__assign(__assign({}, routeMeta), { path: toRelativePath(routeMeta.path, parentPath) }));
|
|
398
|
+
}
|
|
399
|
+
var _loop_2 = function (c) {
|
|
400
|
+
var directChildren = groups.get(c.path);
|
|
401
|
+
if (directChildren && directChildren.length > 0) {
|
|
402
|
+
var selfRoute = normalized.find(function (r) { return r.path === c.path; });
|
|
403
|
+
var nested = buildGroupNode(c.path, selfRoute, directChildren);
|
|
404
|
+
childNodes.push(__assign(__assign({}, nested), { path: toRelativePath(c.path, parentPath) }));
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
childNodes.push(__assign(__assign({}, c), { path: toRelativePath(c.path, parentPath) }));
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
for (var _i = 0, childrenPaths_1 = childrenPaths; _i < childrenPaths_1.length; _i++) {
|
|
411
|
+
var c = childrenPaths_1[_i];
|
|
412
|
+
_loop_2(c);
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
method: "PARENT",
|
|
416
|
+
path: parentPath,
|
|
417
|
+
routes: childNodes,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
var result = [];
|
|
421
|
+
for (var _b = 0, roots_1 = roots; _b < roots_1.length; _b++) {
|
|
422
|
+
var r = roots_1[_b];
|
|
423
|
+
var directChildren = groups.get(r.path);
|
|
424
|
+
if (directChildren && directChildren.length > 0) {
|
|
425
|
+
result.push(buildGroupNode(r.path, r, directChildren));
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
result.push(__assign({}, r));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return result;
|
|
432
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Request, Response, NextFunction } from "express";
|
|
2
|
+
interface TraceletExpressOptions {
|
|
3
|
+
serviceName: string;
|
|
4
|
+
environment?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function traceletMiddleware(options: TraceletExpressOptions): (req: Request, res: Response, next: NextFunction) => void;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.traceletMiddleware = traceletMiddleware;
|
|
4
|
+
var core_1 = require("@tracelet/core");
|
|
5
|
+
var collectedRoutes = [];
|
|
6
|
+
function traceletMiddleware(options) {
|
|
7
|
+
var _a;
|
|
8
|
+
var logger = (0, core_1.createLogger)({
|
|
9
|
+
serviceName: options.serviceName,
|
|
10
|
+
environment: (_a = options.environment) !== null && _a !== void 0 ? _a : "prod",
|
|
11
|
+
});
|
|
12
|
+
return function (req, res, next) {
|
|
13
|
+
var startTime = process.hrtime.bigint();
|
|
14
|
+
var tracingId = logger.createTracingId();
|
|
15
|
+
req.traceletRequestId = tracingId;
|
|
16
|
+
req.traceletTracingId = tracingId;
|
|
17
|
+
req.traceletLogger = logger;
|
|
18
|
+
res.on("finish", function () {
|
|
19
|
+
var _a;
|
|
20
|
+
var path = req.originalUrl.split("?")[0];
|
|
21
|
+
var route = ((_a = req.route) === null || _a === void 0 ? void 0 : _a.path) || req.baseUrl + req.path || req.originalUrl;
|
|
22
|
+
if (path === "/tracelet-docs" || path.startsWith("/tracelet-docs/") || route.startsWith("undefined")) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
var endTime = process.hrtime.bigint();
|
|
26
|
+
var durationMs = Number(endTime - startTime) / 1000000;
|
|
27
|
+
var rawSize = res.getHeader("content-length");
|
|
28
|
+
var responseSize = typeof rawSize === "string" || typeof rawSize === "number"
|
|
29
|
+
? rawSize
|
|
30
|
+
: 0;
|
|
31
|
+
logger.logHttp({
|
|
32
|
+
requestId: tracingId,
|
|
33
|
+
tracingId: tracingId,
|
|
34
|
+
method: req.method,
|
|
35
|
+
route: route,
|
|
36
|
+
statusCode: res.statusCode,
|
|
37
|
+
durationMs: durationMs,
|
|
38
|
+
responseSize: responseSize,
|
|
39
|
+
});
|
|
40
|
+
if (!collectedRoutes.some(function (r) { return r.method === req.method && r.path === route; })) {
|
|
41
|
+
collectedRoutes.push({ method: req.method, path: route });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
next();
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Application } from "express";
|
|
2
|
+
/**
|
|
3
|
+
* Returns all routes registered on the Express app (and nested routers).
|
|
4
|
+
* Uses Express internal router stack; works with Express 4 and 5.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getExpressRoutes(app: Application): {
|
|
7
|
+
method: string;
|
|
8
|
+
path: string;
|
|
9
|
+
}[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getExpressRoutes = getExpressRoutes;
|
|
4
|
+
/**
|
|
5
|
+
* Returns all routes registered on the Express app (and nested routers).
|
|
6
|
+
* Uses Express internal router stack; works with Express 4 and 5.
|
|
7
|
+
*/
|
|
8
|
+
function getExpressRoutes(app) {
|
|
9
|
+
var routes = [];
|
|
10
|
+
var router = app.router;
|
|
11
|
+
if (!(router === null || router === void 0 ? void 0 : router.stack))
|
|
12
|
+
return routes;
|
|
13
|
+
function walk(stack, prefix) {
|
|
14
|
+
var _a;
|
|
15
|
+
if (prefix === void 0) { prefix = ""; }
|
|
16
|
+
for (var _i = 0, stack_1 = stack; _i < stack_1.length; _i++) {
|
|
17
|
+
var layer = stack_1[_i];
|
|
18
|
+
if (layer.route) {
|
|
19
|
+
var path = (prefix + layer.route.path).replace(/\/+/g, "/") || "/";
|
|
20
|
+
var methods = Object.keys(layer.route.methods).filter(function (m) { return m !== "_all"; });
|
|
21
|
+
for (var _b = 0, methods_1 = methods; _b < methods_1.length; _b++) {
|
|
22
|
+
var method = methods_1[_b];
|
|
23
|
+
if (method !== "head") {
|
|
24
|
+
routes.push({ method: method.toUpperCase(), path: path });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else if (layer.name === "router" && ((_a = layer.handle) === null || _a === void 0 ? void 0 : _a.stack)) {
|
|
29
|
+
var segment = typeof layer.path === "string" ? layer.path : "";
|
|
30
|
+
walk(layer.handle.stack, prefix + segment);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
walk(router.stack);
|
|
35
|
+
return routes;
|
|
36
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.auth = void 0;
|
|
7
|
+
var core_1 = require("@tracelet/core");
|
|
8
|
+
var express_1 = __importDefault(require("express"));
|
|
9
|
+
var auth = (0, core_1.createAuth)();
|
|
10
|
+
exports.auth = auth;
|
|
11
|
+
var authRouter = express_1.default.Router();
|
|
12
|
+
authRouter.get("/check-auth", function (_req, res) {
|
|
13
|
+
res.status(200).json({ authRequired: auth.isAuthRequired() });
|
|
14
|
+
});
|
|
15
|
+
/** POST body: { username, password }. Returns { token } (JWT) on success, 401 on failure. */
|
|
16
|
+
authRouter.post("/auth", function (req, res) {
|
|
17
|
+
var _a;
|
|
18
|
+
var _b = (_a = req.body) !== null && _a !== void 0 ? _a : {}, username = _b.username, password = _b.password;
|
|
19
|
+
if (typeof username !== "string" || typeof password !== "string") {
|
|
20
|
+
return res.status(400).json({ error: "username and password required" });
|
|
21
|
+
}
|
|
22
|
+
if (!auth.authenticate(username, password)) {
|
|
23
|
+
return res.status(401).json({ error: "Invalid credentials" });
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
var token = auth.createToken(username);
|
|
27
|
+
return res.status(200).json({ token: token });
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
return res.status(500).json({ error: "Failed to create token" });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
/** GET for backward compat: pass ?username=...&password=... (less secure). Prefer POST /auth. */
|
|
34
|
+
authRouter.get("/auth", function (req, res) {
|
|
35
|
+
var username = req.query.username;
|
|
36
|
+
var password = req.query.password;
|
|
37
|
+
if (!username || !password) {
|
|
38
|
+
return res.status(400).json({ error: "username and password required" });
|
|
39
|
+
}
|
|
40
|
+
if (!auth.authenticate(username, password)) {
|
|
41
|
+
return res.status(401).json({ error: "Invalid credentials" });
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
var token = auth.createToken(username);
|
|
45
|
+
return res.status(200).json({ token: token });
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
return res.status(500).json({ error: "Failed to create token" });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
exports.default = authRouter;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
var core_1 = require("@tracelet/core");
|
|
7
|
+
var express_1 = __importDefault(require("express"));
|
|
8
|
+
var authRouter = express_1.default.Router();
|
|
9
|
+
authRouter.get("/check-auth", function (req, res) {
|
|
10
|
+
var TRACELET_DOC_USERNAME = (0, core_1.getEnv)("TRACELET_DOC_USERNAME");
|
|
11
|
+
var TRACELET_DOC_PASSWORD = (0, core_1.getEnv)("TRACELET_DOC_PASSWORD");
|
|
12
|
+
if (TRACELET_DOC_USERNAME && TRACELET_DOC_PASSWORD) {
|
|
13
|
+
res.status(200).json({ authRequired: true });
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
res.status(200).json({ authRequired: false });
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
authRouter.get("/auth", function (req, res) {
|
|
20
|
+
res.json({ ok: true });
|
|
21
|
+
});
|
|
22
|
+
exports.default = authRouter;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Application } from "express";
|
|
2
|
+
import { type TraceletMeta } from "@tracelet/core";
|
|
3
|
+
/** Options for the tracelet docs UI. */
|
|
4
|
+
export interface TraceletDocOptions {
|
|
5
|
+
/** Path to JSON file for route meta (e.g. tracelet.doc.json). Defaults to `tracelet.doc.json` in process.cwd(). */
|
|
6
|
+
docFilePath?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Mounts the Tracelet docs UI on the given Express app.
|
|
10
|
+
* Creates a route at `path` (default `/tracelet-docs`) serving the built UI and `?json=true` for route meta.
|
|
11
|
+
*/
|
|
12
|
+
export declare function traceletDoc(app: Application, meta?: TraceletMeta[], options?: TraceletDocOptions): void;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.traceletDoc = traceletDoc;
|
|
40
|
+
var express_1 = __importDefault(require("express"));
|
|
41
|
+
var core_1 = require("@tracelet/core");
|
|
42
|
+
var fs_1 = __importDefault(require("fs"));
|
|
43
|
+
var path_1 = __importDefault(require("path"));
|
|
44
|
+
var auth_1 = __importStar(require("./handler/auth"));
|
|
45
|
+
var DEFAULT_DOCS_PATH = "/tracelet-docs";
|
|
46
|
+
/**
|
|
47
|
+
* Mounts the Tracelet docs UI on the given Express app.
|
|
48
|
+
* Creates a route at `path` (default `/tracelet-docs`) serving the built UI and `?json=true` for route meta.
|
|
49
|
+
*/
|
|
50
|
+
function traceletDoc(app, meta, options) {
|
|
51
|
+
var _a;
|
|
52
|
+
if (meta === void 0) { meta = []; }
|
|
53
|
+
if (options === void 0) { options = {}; }
|
|
54
|
+
var routeMeta = (0, core_1.createRouteMeta)();
|
|
55
|
+
var mountPath = DEFAULT_DOCS_PATH;
|
|
56
|
+
var uiPath = path_1.default.resolve((0, core_1.resolveDefaultUiPath)());
|
|
57
|
+
var indexHtml = path_1.default.join(uiPath, "index.html");
|
|
58
|
+
var staticHandler = express_1.default.static(uiPath, { index: false });
|
|
59
|
+
var docFilePath = (_a = options.docFilePath) !== null && _a !== void 0 ? _a : path_1.default.join(process.cwd(), routeMeta.defaultDocFile);
|
|
60
|
+
var resolvedMeta = routeMeta.loadAndMerge(docFilePath, meta !== null && meta !== void 0 ? meta : []);
|
|
61
|
+
var handler = function (req, res, next) {
|
|
62
|
+
if (req.query.json === "true") {
|
|
63
|
+
if (!auth_1.auth.isAuthRequired()) {
|
|
64
|
+
return res.json(routeMeta.resolveTree(resolvedMeta));
|
|
65
|
+
}
|
|
66
|
+
if (auth_1.auth.isAuthRequired() && auth_1.auth.verifyRequest(req.headers.authorization)) {
|
|
67
|
+
return res.json(routeMeta.resolveTree(resolvedMeta));
|
|
68
|
+
}
|
|
69
|
+
return res.status(401).json({ error: "Unauthorized" });
|
|
70
|
+
}
|
|
71
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
72
|
+
return next();
|
|
73
|
+
}
|
|
74
|
+
staticHandler(req, res, function (err) {
|
|
75
|
+
if (err)
|
|
76
|
+
return next(err);
|
|
77
|
+
if (res.headersSent)
|
|
78
|
+
return;
|
|
79
|
+
if (!fs_1.default.existsSync(indexHtml)) {
|
|
80
|
+
return res
|
|
81
|
+
.status(500)
|
|
82
|
+
.set("Content-Type", "text/plain")
|
|
83
|
+
.send("Tracelet UI not found. Build the UI or set uiPath." + indexHtml);
|
|
84
|
+
}
|
|
85
|
+
res.sendFile(indexHtml);
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
app.use(mountPath, auth_1.default);
|
|
89
|
+
app.use(mountPath, handler);
|
|
90
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type express from "express";
|
|
2
|
+
export declare function captureAppMountPaths(app: express.Application): void;
|
|
3
|
+
/** Express router layer (internal shape). */
|
|
4
|
+
interface RouterLayer {
|
|
5
|
+
route?: {
|
|
6
|
+
path: string;
|
|
7
|
+
methods: Record<string, boolean>;
|
|
8
|
+
};
|
|
9
|
+
name?: string;
|
|
10
|
+
path?: string;
|
|
11
|
+
handle?: {
|
|
12
|
+
stack?: RouterLayer[];
|
|
13
|
+
mountpath?: string;
|
|
14
|
+
};
|
|
15
|
+
regexp?: RegExp;
|
|
16
|
+
matchers?: ((path: string) => {
|
|
17
|
+
path: string;
|
|
18
|
+
params: Record<string, string>;
|
|
19
|
+
} | false)[];
|
|
20
|
+
}
|
|
21
|
+
/** Resolver for router mount path: (layer) => path string, or string[] of paths to try / use in order. */
|
|
22
|
+
export type RouterMountPathResolver = ((layer: RouterLayer) => string) | string[];
|
|
23
|
+
/**
|
|
24
|
+
* Returns all routes registered on the Express app (and nested routers).
|
|
25
|
+
* Call captureAppMountPaths(app) right after creating the app and before mounting any routers
|
|
26
|
+
* so nested mount paths are resolved. Or pass routerMountPathResolver to supply mount paths
|
|
27
|
+
* (function or array of path strings to try / use in order).
|
|
28
|
+
*/
|
|
29
|
+
export declare function getExpressRoutes(app: express.Application, routerMountPathResolver?: RouterMountPathResolver): {
|
|
30
|
+
method: string;
|
|
31
|
+
path: string;
|
|
32
|
+
}[];
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.captureAppMountPaths = captureAppMountPaths;
|
|
4
|
+
exports.getExpressRoutes = getExpressRoutes;
|
|
5
|
+
/**
|
|
6
|
+
* Alternative way to get routes: capture mount paths when app.use(path, router) is called.
|
|
7
|
+
* Call captureAppMountPaths(app) right after creating the app and before mounting any routers.
|
|
8
|
+
* Then getExpressRoutes(app) will use the captured paths for router layers (no resolver needed).
|
|
9
|
+
*/
|
|
10
|
+
var mountPathByHandle = new WeakMap();
|
|
11
|
+
function captureAppMountPaths(app) {
|
|
12
|
+
var originalUse = app.use.bind(app);
|
|
13
|
+
app.use = function use() {
|
|
14
|
+
var args = [];
|
|
15
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
16
|
+
args[_i] = arguments[_i];
|
|
17
|
+
}
|
|
18
|
+
var path = typeof args[0] === "string" ? args[0] : "/";
|
|
19
|
+
var start = typeof args[0] === "string" ? 1 : 0;
|
|
20
|
+
for (var i = start; i < args.length; i++) {
|
|
21
|
+
var fn = args[i];
|
|
22
|
+
if (fn && typeof fn === "function")
|
|
23
|
+
mountPathByHandle.set(fn, path);
|
|
24
|
+
}
|
|
25
|
+
return originalUse.apply(this, args);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Regex to extract a mount path from a layer's regexp.source when present.
|
|
30
|
+
*/
|
|
31
|
+
var MOUNT_PATH_FROM_REGEXP_SOURCE = /^\^\\\/((?:[^\\\[\](){}|*+?^$]|\\.)*?)(?:\$|\\\/|\()/;
|
|
32
|
+
/**
|
|
33
|
+
* Get the mount path for a router layer: captured path (from captureAppMountPaths),
|
|
34
|
+
* layer.path, handle.mountpath, regexp extraction, then try matcher with candidates, then resolver.
|
|
35
|
+
*/
|
|
36
|
+
function getMountPath(layer, resolver, mountPathIndex) {
|
|
37
|
+
var _a, _b, _c;
|
|
38
|
+
var handle = layer.handle;
|
|
39
|
+
if (handle && mountPathByHandle.has(handle))
|
|
40
|
+
return mountPathByHandle.get(handle);
|
|
41
|
+
if (typeof layer.path === "string" && layer.path.length > 0)
|
|
42
|
+
return layer.path;
|
|
43
|
+
var h = handle;
|
|
44
|
+
if (h === null || h === void 0 ? void 0 : h.mountpath)
|
|
45
|
+
return h.mountpath;
|
|
46
|
+
var src = (_a = layer.regexp) === null || _a === void 0 ? void 0 : _a.source;
|
|
47
|
+
if (src) {
|
|
48
|
+
var m = src.match(MOUNT_PATH_FROM_REGEXP_SOURCE);
|
|
49
|
+
if (m === null || m === void 0 ? void 0 : m[1])
|
|
50
|
+
return "/" + m[1].replace(/\\\//g, "/");
|
|
51
|
+
}
|
|
52
|
+
var matchers = layer.matchers;
|
|
53
|
+
if (Array.isArray(resolver) && (matchers === null || matchers === void 0 ? void 0 : matchers[0])) {
|
|
54
|
+
for (var _i = 0, resolver_1 = resolver; _i < resolver_1.length; _i++) {
|
|
55
|
+
var candidate = resolver_1[_i];
|
|
56
|
+
if (candidate && matchers[0](candidate))
|
|
57
|
+
return candidate;
|
|
58
|
+
}
|
|
59
|
+
return (_b = resolver[mountPathIndex]) !== null && _b !== void 0 ? _b : "";
|
|
60
|
+
}
|
|
61
|
+
if (resolver === undefined)
|
|
62
|
+
return "";
|
|
63
|
+
return typeof resolver === "function"
|
|
64
|
+
? resolver(layer)
|
|
65
|
+
: (_c = resolver[mountPathIndex]) !== null && _c !== void 0 ? _c : "";
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Returns all routes registered on the Express app (and nested routers).
|
|
69
|
+
* Call captureAppMountPaths(app) right after creating the app and before mounting any routers
|
|
70
|
+
* so nested mount paths are resolved. Or pass routerMountPathResolver to supply mount paths
|
|
71
|
+
* (function or array of path strings to try / use in order).
|
|
72
|
+
*/
|
|
73
|
+
function getExpressRoutes(app, routerMountPathResolver) {
|
|
74
|
+
var routes = [];
|
|
75
|
+
var router = app.router;
|
|
76
|
+
if (!(router === null || router === void 0 ? void 0 : router.stack))
|
|
77
|
+
return routes;
|
|
78
|
+
var mountPathIndex = 0;
|
|
79
|
+
function walk(stack, prefix) {
|
|
80
|
+
var _a, _b;
|
|
81
|
+
if (prefix === void 0) { prefix = ""; }
|
|
82
|
+
for (var _i = 0, stack_1 = stack; _i < stack_1.length; _i++) {
|
|
83
|
+
var layer = stack_1[_i];
|
|
84
|
+
if (layer.route) {
|
|
85
|
+
var path = (prefix + layer.route.path).replace(/\/+/g, "/") || "/";
|
|
86
|
+
var methods = Object.keys(layer.route.methods).filter(function (m) { return m !== "_all"; });
|
|
87
|
+
for (var _c = 0, methods_1 = methods; _c < methods_1.length; _c++) {
|
|
88
|
+
var method = methods_1[_c];
|
|
89
|
+
if (method !== "head")
|
|
90
|
+
routes.push({ method: method.toUpperCase(), path: path });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else if ((layer.name === "router" || ((_a = layer.handle) === null || _a === void 0 ? void 0 : _a.stack)) && ((_b = layer.handle) === null || _b === void 0 ? void 0 : _b.stack)) {
|
|
94
|
+
var segment = getMountPath(layer, routerMountPathResolver, mountPathIndex);
|
|
95
|
+
if (Array.isArray(routerMountPathResolver))
|
|
96
|
+
mountPathIndex++;
|
|
97
|
+
var nextPrefix = (prefix + segment).replace(/\/+/g, "/") || "/";
|
|
98
|
+
walk(layer.handle.stack, nextPrefix);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
walk(router.stack);
|
|
103
|
+
return routes;
|
|
104
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Logger } from "@tracelet/core";
|
|
2
|
+
declare global {
|
|
3
|
+
namespace Express {
|
|
4
|
+
interface Request {
|
|
5
|
+
traceletRequestId?: string;
|
|
6
|
+
traceletTracingId?: string;
|
|
7
|
+
traceletLogger: Logger;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** Re-export meta types from core so consumers can keep importing from @tracelet/express */
|
|
12
|
+
export type { RequestContentType, TraceletMeta, TraceletHttpMethod, TraceletProperty, TraceletResponseProperty, } from "@tracelet/core";
|
|
13
|
+
/** Map of field name to type/schema (query, params – simple key/value) */
|
|
14
|
+
export type TraceletSchema = Record<string, string>;
|
|
15
|
+
export {};
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tracelet/express",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Express middleware for Tracelet",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": ["dist", "src/types.d.ts"],
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc -p tsconfig.build.json"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@tracelet/core": "^0.0.1",
|
|
13
|
+
"@tracelet/ui": "^0.0.1",
|
|
14
|
+
"express": "^5.2.1"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"express": "^4 || ^5"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/tracelethq/tracelet.git",
|
|
23
|
+
"directory": "packages/sdk-express"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/express": "^5.0.6",
|
|
27
|
+
"@types/node": "^25.2.1",
|
|
28
|
+
"typescript": "^5.9.3"
|
|
29
|
+
}
|
|
30
|
+
}
|