hasancode-api-docs 1.0.4 → 1.0.8
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 +5 -5
- package/dist/middleware/ApiDoc.d.ts +0 -37
- package/dist/middleware/ApiDoc.js +150 -180
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ If you use TypeScript decorators, enable these in your `tsconfig.json`:
|
|
|
41
41
|
|
|
42
42
|
```ts
|
|
43
43
|
import express from "express";
|
|
44
|
-
import { ApiDoc, doc } from "api-docs";
|
|
44
|
+
import { ApiDoc, doc } from "hasancode-api-docs";
|
|
45
45
|
|
|
46
46
|
const app = express();
|
|
47
47
|
app.use(express.json());
|
|
@@ -82,7 +82,7 @@ app.listen(5000, () => {
|
|
|
82
82
|
This is the lightweight style (similar to `example/server.js`).
|
|
83
83
|
|
|
84
84
|
```ts
|
|
85
|
-
import { doc } from "api-docs";
|
|
85
|
+
import { doc } from "hasancode-api-docs";
|
|
86
86
|
|
|
87
87
|
doc({
|
|
88
88
|
method: "get",
|
|
@@ -120,7 +120,7 @@ doc({
|
|
|
120
120
|
This is the advanced object-schema style (similar to `example2/src/app.ts`).
|
|
121
121
|
|
|
122
122
|
```ts
|
|
123
|
-
import { doc } from "api-docs";
|
|
123
|
+
import { doc } from "hasancode-api-docs";
|
|
124
124
|
|
|
125
125
|
doc({
|
|
126
126
|
method: "patch",
|
|
@@ -219,7 +219,7 @@ import {
|
|
|
219
219
|
Description,
|
|
220
220
|
Tags,
|
|
221
221
|
Auth,
|
|
222
|
-
} from "api-docs";
|
|
222
|
+
} from "hasancode-api-docs";
|
|
223
223
|
|
|
224
224
|
class UserController {
|
|
225
225
|
@Tags("Users")
|
|
@@ -359,7 +359,7 @@ new ApiDoc({
|
|
|
359
359
|
Import and use `CODE_THEMES`:
|
|
360
360
|
|
|
361
361
|
```ts
|
|
362
|
-
import { CODE_THEMES } from "api-docs";
|
|
362
|
+
import { CODE_THEMES } from "hasancode-api-docs";
|
|
363
363
|
|
|
364
364
|
const apiDocs = new ApiDoc({
|
|
365
365
|
title: "My API",
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { Router } from "express";
|
|
2
2
|
import { RouteMetadata } from "../registry/RouteRegistry";
|
|
3
3
|
import { CodeThemeValues } from "../code-theme";
|
|
4
|
-
/**
|
|
5
|
-
* API Documentation configuration
|
|
6
|
-
*/
|
|
7
4
|
export interface ApiDocConfig {
|
|
8
5
|
title: string;
|
|
9
6
|
version: string;
|
|
@@ -22,54 +19,20 @@ export interface ApiDocConfig {
|
|
|
22
19
|
bearerFormat?: string;
|
|
23
20
|
};
|
|
24
21
|
}
|
|
25
|
-
/**
|
|
26
|
-
* Main API Documentation class
|
|
27
|
-
*/
|
|
28
22
|
export declare class ApiDoc {
|
|
29
23
|
private config;
|
|
30
24
|
private clientPath;
|
|
31
25
|
private readonly DOCS_PATH;
|
|
32
26
|
private configReady;
|
|
33
27
|
constructor(config: ApiDocConfig);
|
|
34
|
-
/**
|
|
35
|
-
* Normalize configuration with defaults
|
|
36
|
-
*/
|
|
37
28
|
private normalizeConfig;
|
|
38
|
-
/**
|
|
39
|
-
* Resolve client path
|
|
40
|
-
*/
|
|
41
29
|
private resolveClientPath;
|
|
42
|
-
/**
|
|
43
|
-
* Convert route metadata to API documentation format
|
|
44
|
-
*/
|
|
45
30
|
private convertRoutesToSections;
|
|
46
|
-
/**
|
|
47
|
-
* Convert single route to section format
|
|
48
|
-
*/
|
|
49
31
|
private convertRouteToSection;
|
|
50
|
-
/**
|
|
51
|
-
* Get Express middleware - automatically mounts at /api-docs
|
|
52
|
-
* Usage: app.use(apiDoc.middleware())
|
|
53
|
-
*/
|
|
54
32
|
middleware(): Router;
|
|
55
|
-
/**
|
|
56
|
-
* Serve configuration
|
|
57
|
-
*/
|
|
58
33
|
private serveConfig;
|
|
59
|
-
/**
|
|
60
|
-
* Serve the React app
|
|
61
|
-
*/
|
|
62
34
|
private serveApp;
|
|
63
|
-
/**
|
|
64
|
-
* Get all registered routes
|
|
65
|
-
*/
|
|
66
35
|
getRoutes(): RouteMetadata[];
|
|
67
|
-
/**
|
|
68
|
-
* Clear all routes (useful for testing)
|
|
69
|
-
*/
|
|
70
36
|
clearRoutes(): void;
|
|
71
|
-
/**
|
|
72
|
-
* Get the documentation path
|
|
73
|
-
*/
|
|
74
37
|
getDocsPath(): string;
|
|
75
38
|
}
|
|
@@ -49,30 +49,23 @@ const fs = __importStar(require("fs"));
|
|
|
49
49
|
const marked_1 = require("marked");
|
|
50
50
|
const RouteRegistry_1 = require("../registry/RouteRegistry");
|
|
51
51
|
const code_theme_1 = require("../code-theme");
|
|
52
|
-
/**
|
|
53
|
-
* Main API Documentation class
|
|
54
|
-
*/
|
|
55
52
|
class ApiDoc {
|
|
56
53
|
constructor(config) {
|
|
57
|
-
this.DOCS_PATH = "/api-docs";
|
|
54
|
+
this.DOCS_PATH = "/api-docs";
|
|
58
55
|
this.clientPath = this.resolveClientPath();
|
|
59
|
-
this.config = config;
|
|
56
|
+
this.config = config;
|
|
60
57
|
this.configReady = this.normalizeConfig(config);
|
|
61
58
|
}
|
|
62
|
-
/**
|
|
63
|
-
* Normalize configuration with defaults
|
|
64
|
-
*/
|
|
65
59
|
normalizeConfig(config) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
60
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
61
|
+
// Convert description to HTML if it exists
|
|
62
|
+
if (config === null || config === void 0 ? void 0 : config.description) {
|
|
63
|
+
const html = yield marked_1.marked.parse(config.description.toString());
|
|
64
|
+
config.description = '<div class="md__code">' + html + "</div>";
|
|
65
|
+
}
|
|
66
|
+
this.config = Object.assign(Object.assign({}, config), { baseUrl: config.baseUrl || "http://localhost:5000", theme: Object.assign({ primaryColor: "#3b82f6", secondaryColor: "#8b5cf6", accentColor: "#10b981", backgroundColor: "#ffffff", sidebarBackgroundColor: "#1f2937", codeTheme: code_theme_1.CODE_THEMES.VITESSE_BLACK }, config.theme) });
|
|
67
|
+
});
|
|
72
68
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Resolve client path
|
|
75
|
-
*/
|
|
76
69
|
resolveClientPath() {
|
|
77
70
|
const possiblePaths = [
|
|
78
71
|
path.join(__dirname, "../../client/dist"),
|
|
@@ -87,219 +80,196 @@ class ApiDoc {
|
|
|
87
80
|
}
|
|
88
81
|
return path.join(__dirname, "../../client/dist");
|
|
89
82
|
}
|
|
90
|
-
/**
|
|
91
|
-
* Convert route metadata to API documentation format
|
|
92
|
-
*/
|
|
93
83
|
convertRoutesToSections() {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
84
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
85
|
+
const routes = RouteRegistry_1.routeRegistry.getAll();
|
|
86
|
+
const sections = [];
|
|
87
|
+
const grouped = new Map();
|
|
88
|
+
const untagged = [];
|
|
89
|
+
for (const route of routes) {
|
|
90
|
+
if (route.tags && route.tags.length > 0) {
|
|
91
|
+
for (const tag of route.tags) {
|
|
92
|
+
if (!grouped.has(tag))
|
|
93
|
+
grouped.set(tag, []);
|
|
94
|
+
grouped.get(tag).push(route);
|
|
104
95
|
}
|
|
105
|
-
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
untagged.push(route);
|
|
106
99
|
}
|
|
107
100
|
}
|
|
108
|
-
|
|
109
|
-
|
|
101
|
+
for (const [tag, tagRoutes] of grouped.entries()) {
|
|
102
|
+
const items = yield Promise.all(tagRoutes.map((route) => this.convertRouteToSection(route)));
|
|
103
|
+
sections.push({
|
|
104
|
+
name: tag.toLowerCase().replace(/\s+/g, "-"),
|
|
105
|
+
label: tag,
|
|
106
|
+
type: "GROUP",
|
|
107
|
+
items,
|
|
108
|
+
});
|
|
110
109
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (untagged.length > 0) {
|
|
123
|
-
sections.push({
|
|
124
|
-
name: "other",
|
|
125
|
-
label: "Other Endpoints",
|
|
126
|
-
type: "GROUP",
|
|
127
|
-
items: untagged.map((route) => this.convertRouteToSection(route)),
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
return sections;
|
|
110
|
+
if (untagged.length > 0) {
|
|
111
|
+
const items = yield Promise.all(untagged.map((route) => this.convertRouteToSection(route)));
|
|
112
|
+
sections.push({
|
|
113
|
+
name: "other",
|
|
114
|
+
label: "Other Endpoints",
|
|
115
|
+
type: "GROUP",
|
|
116
|
+
items,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return sections;
|
|
120
|
+
});
|
|
131
121
|
}
|
|
132
|
-
/**
|
|
133
|
-
* Convert single route to section format
|
|
134
|
-
*/
|
|
135
122
|
convertRouteToSection(route) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
route
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
route
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
route
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
section.api.params = route.params;
|
|
168
|
-
}
|
|
169
|
-
if (route.query && route.query.length > 0) {
|
|
170
|
-
section.api.query = route.query;
|
|
171
|
-
}
|
|
172
|
-
if (route.headers && route.headers.length > 0) {
|
|
173
|
-
section.api.headers = route.headers;
|
|
174
|
-
}
|
|
175
|
-
if (route.body) {
|
|
176
|
-
section.api.body = {
|
|
177
|
-
contentType: route.body.contentType,
|
|
178
|
-
required: route.body.required,
|
|
179
|
-
description: route.body.description,
|
|
180
|
-
schema: route.body.schema,
|
|
181
|
-
example: route.body.example,
|
|
123
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
124
|
+
var _a, _b, _c, _d;
|
|
125
|
+
if (route === null || route === void 0 ? void 0 : route.description) {
|
|
126
|
+
const parsed = yield marked_1.marked.parse(route.description.toString());
|
|
127
|
+
route.description = '<div class="md__code">' + parsed + "</div>";
|
|
128
|
+
}
|
|
129
|
+
if (route === null || route === void 0 ? void 0 : route.summary) {
|
|
130
|
+
const parsed = yield marked_1.marked.parse(route.summary.toString());
|
|
131
|
+
route.summary = '<div class="md__code">' + parsed + "</div>";
|
|
132
|
+
}
|
|
133
|
+
if (route === null || route === void 0 ? void 0 : route.note) {
|
|
134
|
+
const parsed = yield marked_1.marked.parse(route.note.toString());
|
|
135
|
+
route.note = '<div class="md__code">' + parsed + "</div>";
|
|
136
|
+
}
|
|
137
|
+
const section = {
|
|
138
|
+
name: `${route.method}${route.path}`
|
|
139
|
+
.replace(/[/:]/g, "-")
|
|
140
|
+
.replace("--", "-"),
|
|
141
|
+
label: `${route.method.toUpperCase()} ${route.path}`,
|
|
142
|
+
type: "ITEM",
|
|
143
|
+
content: route.description,
|
|
144
|
+
api: {
|
|
145
|
+
method: route.method,
|
|
146
|
+
path: route.path,
|
|
147
|
+
summary: route.summary,
|
|
148
|
+
description: route.description,
|
|
149
|
+
deprecated: route.deprecated,
|
|
150
|
+
note: route.note,
|
|
151
|
+
tags: route.tags,
|
|
152
|
+
auth: route.auth,
|
|
153
|
+
},
|
|
182
154
|
};
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
155
|
+
if ((_a = route.params) === null || _a === void 0 ? void 0 : _a.length)
|
|
156
|
+
section.api.params = route.params;
|
|
157
|
+
if ((_b = route.query) === null || _b === void 0 ? void 0 : _b.length)
|
|
158
|
+
section.api.query = route.query;
|
|
159
|
+
if ((_c = route.headers) === null || _c === void 0 ? void 0 : _c.length)
|
|
160
|
+
section.api.headers = route.headers;
|
|
161
|
+
if (route.body) {
|
|
162
|
+
section.api.body = {
|
|
163
|
+
contentType: route.body.contentType,
|
|
164
|
+
required: route.body.required,
|
|
165
|
+
description: route.body.description,
|
|
166
|
+
schema: route.body.schema,
|
|
167
|
+
example: route.body.example,
|
|
192
168
|
};
|
|
193
169
|
}
|
|
194
|
-
|
|
195
|
-
|
|
170
|
+
if ((_d = route.responses) === null || _d === void 0 ? void 0 : _d.length) {
|
|
171
|
+
section.api.responses = {};
|
|
172
|
+
for (const response of route.responses) {
|
|
173
|
+
section.api.responses[response.statusCode] = {
|
|
174
|
+
description: response.description,
|
|
175
|
+
contentType: response.contentType,
|
|
176
|
+
schema: response.schema,
|
|
177
|
+
example: response.example,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return section;
|
|
182
|
+
});
|
|
196
183
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
*/
|
|
184
|
+
// ══════════════════════════════════════════════════════════════
|
|
185
|
+
// FIXED: Static file serving with correct MIME types
|
|
186
|
+
// ══════════════════════════════════════════════════════════════
|
|
201
187
|
middleware() {
|
|
202
188
|
const router = (0, express_1.Router)();
|
|
189
|
+
// ── 1. Serve config.json (MUST come before static assets) ────
|
|
203
190
|
router.get(`${this.DOCS_PATH}/config.json`, this.serveConfig.bind(this));
|
|
204
|
-
|
|
191
|
+
// ── 2. Serve static assets (CSS, JS, etc.) ───────────────────
|
|
192
|
+
// CRITICAL: Use absolute path match to prevent wildcard route from catching it
|
|
193
|
+
router.use(`${this.DOCS_PATH}/assets`, (req, res, next) => {
|
|
194
|
+
// Manually set correct MIME types before expressStatic processes
|
|
195
|
+
const ext = path.extname(req.path).toLowerCase();
|
|
196
|
+
if (ext === ".js") {
|
|
197
|
+
res.setHeader("Content-Type", "application/javascript; charset=utf-8");
|
|
198
|
+
}
|
|
199
|
+
else if (ext === ".css") {
|
|
200
|
+
res.setHeader("Content-Type", "text/css; charset=utf-8");
|
|
201
|
+
}
|
|
202
|
+
else if (ext === ".map") {
|
|
203
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
204
|
+
}
|
|
205
|
+
next();
|
|
206
|
+
}, (0, express_1.static)(path.join(this.clientPath, "assets"), {
|
|
205
207
|
maxAge: "1d",
|
|
206
208
|
etag: true,
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (path.endsWith(".js")) {
|
|
210
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
211
|
-
}
|
|
212
|
-
else if (path.endsWith(".css")) {
|
|
213
|
-
res.setHeader("Content-Type", "text/css");
|
|
214
|
-
}
|
|
215
|
-
},
|
|
209
|
+
index: false, // Don't serve index.html from assets
|
|
210
|
+
fallthrough: false, // Return 404 if file not found
|
|
216
211
|
}));
|
|
217
|
-
|
|
212
|
+
// ── 3. Serve React app (catch-all, MUST come last) ───────────
|
|
213
|
+
router.get(`${this.DOCS_PATH}*`, this.serveApp.bind(this));
|
|
218
214
|
return router;
|
|
219
215
|
}
|
|
220
|
-
/**
|
|
221
|
-
* Serve configuration
|
|
222
|
-
*/
|
|
223
216
|
serveConfig(req, res) {
|
|
224
217
|
return __awaiter(this, void 0, void 0, function* () {
|
|
225
|
-
// Wait for config to be ready
|
|
226
218
|
yield this.configReady;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const runtimeConfig = {
|
|
219
|
+
const sections = yield this.convertRoutesToSections();
|
|
220
|
+
res.json({
|
|
230
221
|
config: Object.assign(Object.assign({}, this.config), { sections }),
|
|
231
222
|
basePath: this.DOCS_PATH,
|
|
232
|
-
};
|
|
233
|
-
res.json(runtimeConfig);
|
|
223
|
+
});
|
|
234
224
|
});
|
|
235
225
|
}
|
|
236
|
-
/**
|
|
237
|
-
* Serve the React app
|
|
238
|
-
*/
|
|
239
226
|
serveApp(req, res) {
|
|
240
227
|
const indexPath = path.join(this.clientPath, "index.html");
|
|
241
228
|
if (fs.existsSync(indexPath)) {
|
|
242
229
|
let html = fs.readFileSync(indexPath, "utf-8");
|
|
243
|
-
// Inject base tag with trailing slash for proper asset resolution
|
|
244
230
|
const baseTag = `<base href="${this.DOCS_PATH}/">`;
|
|
245
|
-
// Insert base tag right after <head> or before any other tags
|
|
246
231
|
if (html.includes("<base")) {
|
|
247
|
-
// Replace existing base tag
|
|
248
232
|
html = html.replace(/<base[^>]*>/i, baseTag);
|
|
249
233
|
}
|
|
250
234
|
else {
|
|
251
|
-
// Insert new base tag
|
|
252
235
|
html = html.replace(/<head>/i, `<head>${baseTag}`);
|
|
253
236
|
}
|
|
254
|
-
res.setHeader("Content-Type", "text/html");
|
|
237
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
255
238
|
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
256
239
|
res.send(html);
|
|
257
240
|
}
|
|
258
241
|
else {
|
|
259
242
|
res.status(404).send(`
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
<p><small>Looking at: ${this.clientPath}</small></p>
|
|
283
|
-
</body>
|
|
284
|
-
</html>
|
|
285
|
-
`);
|
|
243
|
+
<html>
|
|
244
|
+
<head>
|
|
245
|
+
<title>API Docs - Not Built</title>
|
|
246
|
+
<style>
|
|
247
|
+
body {
|
|
248
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
249
|
+
max-width: 600px;
|
|
250
|
+
margin: 100px auto;
|
|
251
|
+
padding: 20px;
|
|
252
|
+
text-align: center;
|
|
253
|
+
}
|
|
254
|
+
h1 { color: #e53e3e; }
|
|
255
|
+
code { background: #f7fafc; padding: 2px 6px; border-radius: 4px; }
|
|
256
|
+
</style>
|
|
257
|
+
</head>
|
|
258
|
+
<body>
|
|
259
|
+
<h1>⚠️ Client Not Built</h1>
|
|
260
|
+
<p>Run <code>npm run build</code> first.</p>
|
|
261
|
+
<small>Looking at: ${this.clientPath}</small>
|
|
262
|
+
</body>
|
|
263
|
+
</html>
|
|
264
|
+
`);
|
|
286
265
|
}
|
|
287
266
|
}
|
|
288
|
-
/**
|
|
289
|
-
* Get all registered routes
|
|
290
|
-
*/
|
|
291
267
|
getRoutes() {
|
|
292
268
|
return RouteRegistry_1.routeRegistry.getAll();
|
|
293
269
|
}
|
|
294
|
-
/**
|
|
295
|
-
* Clear all routes (useful for testing)
|
|
296
|
-
*/
|
|
297
270
|
clearRoutes() {
|
|
298
271
|
RouteRegistry_1.routeRegistry.clear();
|
|
299
272
|
}
|
|
300
|
-
/**
|
|
301
|
-
* Get the documentation path
|
|
302
|
-
*/
|
|
303
273
|
getDocsPath() {
|
|
304
274
|
return this.DOCS_PATH;
|
|
305
275
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hasancode-api-docs",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "A simple and easy to use API documentation generator for Express.js",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
],
|
|
17
17
|
"keywords": [
|
|
18
18
|
"api",
|
|
19
|
+
"api-doc",
|
|
20
|
+
"api-docs",
|
|
19
21
|
"documentation",
|
|
20
22
|
"express",
|
|
21
23
|
"typescript",
|
|
@@ -47,7 +49,7 @@
|
|
|
47
49
|
"reflect-metadata": "^0.2.2"
|
|
48
50
|
},
|
|
49
51
|
"peerDependencies": {
|
|
50
|
-
"express": "
|
|
52
|
+
"express": ">=4.0.0"
|
|
51
53
|
},
|
|
52
54
|
"engines": {
|
|
53
55
|
"node": ">=20.0.0"
|