exisjs 0.7.6 → 0.7.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/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +12 -0
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/clean.d.ts +5 -0
- package/dist/cli/commands/clean.d.ts.map +1 -0
- package/dist/cli/commands/clean.js +30 -0
- package/dist/cli/commands/clean.js.map +1 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +15 -1
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/generate.d.ts +6 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +57 -2
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/index.js +16 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/manifest.d.ts +1 -0
- package/dist/cli/manifest.d.ts.map +1 -1
- package/dist/cli/manifest.js +161 -23
- package/dist/cli/manifest.js.map +1 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +9 -0
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +4 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/cron/expressions.d.ts +45 -0
- package/dist/cron/expressions.d.ts.map +1 -0
- package/dist/cron/expressions.js +87 -0
- package/dist/cron/expressions.js.map +1 -0
- package/dist/cron/index.d.ts +6 -0
- package/dist/cron/index.d.ts.map +1 -0
- package/dist/cron/index.js +22 -0
- package/dist/cron/index.js.map +1 -0
- package/dist/cron/job.d.ts +50 -0
- package/dist/cron/job.d.ts.map +1 -0
- package/dist/cron/job.js +247 -0
- package/dist/cron/job.js.map +1 -0
- package/dist/cron/manager.d.ts +105 -0
- package/dist/cron/manager.d.ts.map +1 -0
- package/dist/cron/manager.js +245 -0
- package/dist/cron/manager.js.map +1 -0
- package/dist/cron/parser.d.ts +22 -0
- package/dist/cron/parser.d.ts.map +1 -0
- package/dist/cron/parser.js +269 -0
- package/dist/cron/parser.js.map +1 -0
- package/dist/cron/types.d.ts +76 -0
- package/dist/cron/types.d.ts.map +1 -0
- package/dist/cron/types.js +3 -0
- package/dist/cron/types.js.map +1 -0
- package/dist/decorators/constants.d.ts +2 -0
- package/dist/decorators/constants.d.ts.map +1 -1
- package/dist/decorators/constants.js +3 -1
- package/dist/decorators/constants.js.map +1 -1
- package/dist/decorators/core.d.ts +29 -11
- package/dist/decorators/core.d.ts.map +1 -1
- package/dist/decorators/core.js +49 -11
- package/dist/decorators/core.js.map +1 -1
- package/dist/decorators/cron.d.ts +57 -0
- package/dist/decorators/cron.d.ts.map +1 -0
- package/dist/decorators/cron.js +143 -0
- package/dist/decorators/cron.js.map +1 -0
- package/dist/decorators/index.d.ts +2 -0
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/decorators/index.js +2 -0
- package/dist/decorators/index.js.map +1 -1
- package/dist/decorators/params.d.ts +97 -2
- package/dist/decorators/params.d.ts.map +1 -1
- package/dist/decorators/params.js +94 -2
- package/dist/decorators/params.js.map +1 -1
- package/dist/decorators/routing.d.ts +81 -16
- package/dist/decorators/routing.d.ts.map +1 -1
- package/dist/decorators/routing.js +81 -16
- package/dist/decorators/routing.js.map +1 -1
- package/dist/docs/content/cron.mdx +196 -0
- package/dist/docs/content/errors.mdx +58 -9
- package/dist/docs/content/structure.mdx +1 -1
- package/dist/docs/content/swagger.mdx +289 -0
- package/dist/docs/reference/cron.mdx +300 -0
- package/dist/docs/reference/decorators.mdx +8 -0
- package/dist/docs/reference/error.mdx +44 -0
- package/dist/docs/reference/swagger.mdx +226 -0
- package/dist/docs/reference/validator.mdx +1 -1
- package/dist/router/boundary.d.ts +24 -2
- package/dist/router/boundary.d.ts.map +1 -1
- package/dist/router/boundary.js +24 -2
- package/dist/router/boundary.js.map +1 -1
- package/dist/router/controller-registrar.d.ts.map +1 -1
- package/dist/router/controller-registrar.js +52 -3
- package/dist/router/controller-registrar.js.map +1 -1
- package/dist/router/route-builder.d.ts +196 -33
- package/dist/router/route-builder.d.ts.map +1 -1
- package/dist/router/route-builder.js +171 -11
- package/dist/router/route-builder.js.map +1 -1
- package/dist/router/route-scanner.d.ts +6 -0
- package/dist/router/route-scanner.d.ts.map +1 -1
- package/dist/router/route-scanner.js +69 -812
- package/dist/router/route-scanner.js.map +1 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +27 -0
- package/dist/router/router.js.map +1 -1
- package/dist/router/scanner/boundary-loader.d.ts +18 -0
- package/dist/router/scanner/boundary-loader.d.ts.map +1 -0
- package/dist/router/scanner/boundary-loader.js +539 -0
- package/dist/router/scanner/boundary-loader.js.map +1 -0
- package/dist/router/scanner/cron-mounter.d.ts +15 -0
- package/dist/router/scanner/cron-mounter.d.ts.map +1 -0
- package/dist/router/scanner/cron-mounter.js +216 -0
- package/dist/router/scanner/cron-mounter.js.map +1 -0
- package/dist/router/scanner/error-mounter.d.ts +14 -0
- package/dist/router/scanner/error-mounter.d.ts.map +1 -0
- package/dist/router/scanner/error-mounter.js +200 -0
- package/dist/router/scanner/error-mounter.js.map +1 -0
- package/dist/router/scanner/fs-scanner.d.ts +25 -0
- package/dist/router/scanner/fs-scanner.d.ts.map +1 -0
- package/dist/router/scanner/fs-scanner.js +115 -0
- package/dist/router/scanner/fs-scanner.js.map +1 -0
- package/dist/router/scanner/functional-compiler.d.ts +9 -0
- package/dist/router/scanner/functional-compiler.d.ts.map +1 -0
- package/dist/router/scanner/functional-compiler.js +233 -0
- package/dist/router/scanner/functional-compiler.js.map +1 -0
- package/dist/router/scanner/index.d.ts +9 -0
- package/dist/router/scanner/index.d.ts.map +1 -0
- package/dist/router/scanner/index.js +19 -0
- package/dist/router/scanner/index.js.map +1 -0
- package/dist/router/types.d.ts +15 -0
- package/dist/router/types.d.ts.map +1 -1
- package/dist/server/app.d.ts +2 -0
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +3 -0
- package/dist/server/app.js.map +1 -1
- package/dist/server/helpers/body-parser.d.ts +18 -0
- package/dist/server/helpers/body-parser.d.ts.map +1 -0
- package/dist/server/helpers/body-parser.js +246 -0
- package/dist/server/helpers/body-parser.js.map +1 -0
- package/dist/server/helpers/cookie.d.ts +4 -0
- package/dist/server/helpers/cookie.d.ts.map +1 -0
- package/dist/server/helpers/cookie.js +61 -0
- package/dist/server/helpers/cookie.js.map +1 -0
- package/dist/server/helpers/etag.d.ts +3 -0
- package/dist/server/helpers/etag.d.ts.map +1 -0
- package/dist/server/helpers/etag.js +25 -0
- package/dist/server/helpers/etag.js.map +1 -0
- package/dist/server/helpers/index.d.ts +6 -0
- package/dist/server/helpers/index.d.ts.map +1 -0
- package/dist/server/helpers/index.js +21 -0
- package/dist/server/helpers/index.js.map +1 -0
- package/dist/server/helpers/ip.d.ts +9 -0
- package/dist/server/helpers/ip.d.ts.map +1 -0
- package/dist/server/helpers/ip.js +62 -0
- package/dist/server/helpers/ip.js.map +1 -0
- package/dist/server/helpers/stream.d.ts +4 -0
- package/dist/server/helpers/stream.d.ts.map +1 -0
- package/dist/server/helpers/stream.js +193 -0
- package/dist/server/helpers/stream.js.map +1 -0
- package/dist/server/request.d.ts +1 -139
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/request.js +19 -433
- package/dist/server/request.js.map +1 -1
- package/dist/server/response.d.ts +105 -158
- package/dist/server/response.d.ts.map +1 -1
- package/dist/server/response.js +121 -423
- package/dist/server/response.js.map +1 -1
- package/dist/swagger/decorators.d.ts +81 -0
- package/dist/swagger/decorators.d.ts.map +1 -0
- package/dist/swagger/decorators.js +172 -0
- package/dist/swagger/decorators.js.map +1 -0
- package/dist/swagger/generator.d.ts +19 -0
- package/dist/swagger/generator.d.ts.map +1 -0
- package/dist/swagger/generator.js +344 -0
- package/dist/swagger/generator.js.map +1 -0
- package/dist/swagger/index.d.ts +31 -0
- package/dist/swagger/index.d.ts.map +1 -0
- package/dist/swagger/index.js +53 -0
- package/dist/swagger/index.js.map +1 -0
- package/dist/swagger/mounter.d.ts +7 -0
- package/dist/swagger/mounter.d.ts.map +1 -0
- package/dist/swagger/mounter.js +56 -0
- package/dist/swagger/mounter.js.map +1 -0
- package/dist/swagger/types.d.ts +190 -0
- package/dist/swagger/types.d.ts.map +1 -0
- package/dist/swagger/types.js +3 -0
- package/dist/swagger/types.js.map +1 -0
- package/dist/swagger/ui.d.ts +6 -0
- package/dist/swagger/ui.d.ts.map +1 -0
- package/dist/swagger/ui.js +118 -0
- package/dist/swagger/ui.js.map +1 -0
- package/dist/validator/builder.d.ts +223 -0
- package/dist/validator/builder.d.ts.map +1 -0
- package/dist/validator/builder.js +517 -0
- package/dist/validator/builder.js.map +1 -0
- package/dist/validator/engine.d.ts +19 -0
- package/dist/validator/engine.d.ts.map +1 -0
- package/dist/validator/engine.js +568 -0
- package/dist/validator/engine.js.map +1 -0
- package/dist/validator/error.d.ts +12 -0
- package/dist/validator/error.d.ts.map +1 -0
- package/dist/validator/error.js +63 -0
- package/dist/validator/error.js.map +1 -0
- package/dist/validator/pagination.d.ts +40 -0
- package/dist/validator/pagination.d.ts.map +1 -0
- package/dist/validator/pagination.js +60 -0
- package/dist/validator/pagination.js.map +1 -0
- package/dist/validator/tex.d.ts +6 -158
- package/dist/validator/tex.d.ts.map +1 -1
- package/dist/validator/tex.js +14 -971
- package/dist/validator/tex.js.map +1 -1
- package/dist/validator/types.d.ts +83 -0
- package/dist/validator/types.d.ts.map +1 -0
- package/dist/validator/types.js +3 -0
- package/dist/validator/types.js.map +1 -0
- package/package.json +9 -1
|
@@ -36,10 +36,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.RouteScanner = void 0;
|
|
37
37
|
const router_1 = require("../router/router");
|
|
38
38
|
const middleware_1 = require("../middleware/middleware");
|
|
39
|
-
const
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
40
|
const node_url_1 = require("node:url");
|
|
41
|
-
const context_1 = require("../server/context");
|
|
42
41
|
const overlay_1 = require("../error/overlay");
|
|
42
|
+
const scanner_1 = require("./scanner");
|
|
43
43
|
class RouteScanner {
|
|
44
44
|
constructor(app) {
|
|
45
45
|
this.app = app;
|
|
@@ -50,17 +50,17 @@ class RouteScanner {
|
|
|
50
50
|
this.globalParadigm = null;
|
|
51
51
|
}
|
|
52
52
|
async loadAllRoutes() {
|
|
53
|
-
for (const [
|
|
53
|
+
for (const [, entry] of this.lazyRoutes.entries()) {
|
|
54
54
|
if (!entry.loaded) {
|
|
55
55
|
entry.loaded = true;
|
|
56
|
-
this.app.getRouter().removeRoutesBySource('lazy:' + filePath);
|
|
57
|
-
const routePath = this.routeMap.get(filePath);
|
|
56
|
+
this.app.getRouter().removeRoutesBySource('lazy:' + entry.filePath);
|
|
57
|
+
const routePath = this.routeMap.get(entry.filePath);
|
|
58
58
|
if (routePath) {
|
|
59
59
|
try {
|
|
60
|
-
await this.mountRouteFile(filePath, routePath);
|
|
60
|
+
await this.mountRouteFile(entry.filePath, routePath);
|
|
61
61
|
}
|
|
62
62
|
catch (e) {
|
|
63
|
-
this.app.log.error({ err: e, file: filePath }, 'Failed to eager-load route');
|
|
63
|
+
this.app.log.error({ err: e, file: entry.filePath }, 'Failed to eager-load route');
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
}
|
|
@@ -80,6 +80,8 @@ class RouteScanner {
|
|
|
80
80
|
}
|
|
81
81
|
// Try to load the pre-built manifest first (O(1) boot)
|
|
82
82
|
let manifest = undefined;
|
|
83
|
+
let manifestErrorHandler = undefined;
|
|
84
|
+
let manifestCronJobs = undefined;
|
|
83
85
|
// STANDALONE MODE: If the bundler statically injected the manifest, skip filesystem!
|
|
84
86
|
if (globalThis.__EXIS_STANDALONE_MANIFEST__) {
|
|
85
87
|
manifest = globalThis.__EXIS_STANDALONE_MANIFEST__;
|
|
@@ -101,6 +103,8 @@ class RouteScanner {
|
|
|
101
103
|
mod = await dynamicImport(url);
|
|
102
104
|
}
|
|
103
105
|
manifest = mod.manifest;
|
|
106
|
+
manifestErrorHandler = mod.errorHandler;
|
|
107
|
+
manifestCronJobs = mod.cronJobs;
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
catch (err) {
|
|
@@ -112,13 +116,25 @@ class RouteScanner {
|
|
|
112
116
|
const searchDirs = isProd
|
|
113
117
|
? [
|
|
114
118
|
path.join(root, '.exis', 'server', 'src', 'http'),
|
|
115
|
-
path.join(root, 'dist', 'src', 'http'),
|
|
116
119
|
path.join(root, 'src', 'http'),
|
|
117
120
|
]
|
|
118
121
|
: [path.join(root, 'src', 'http')];
|
|
119
122
|
if (Array.isArray(manifest)) {
|
|
120
|
-
|
|
121
|
-
|
|
123
|
+
if (manifestErrorHandler !== undefined) {
|
|
124
|
+
if (manifestErrorHandler) {
|
|
125
|
+
this.mountErrorHandlerFromModule(manifestErrorHandler, 'manifest:error');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
for (const dir of searchDirs) {
|
|
130
|
+
await this.mountErrorHandler(dir);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (Array.isArray(manifestCronJobs)) {
|
|
134
|
+
this.mountCronJobsFromEntries(manifestCronJobs);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
await this.mountCronJobs(root);
|
|
122
138
|
}
|
|
123
139
|
for (const entry of manifest) {
|
|
124
140
|
const { routePath, module: routeMod, filePath } = entry;
|
|
@@ -178,25 +194,20 @@ class RouteScanner {
|
|
|
178
194
|
/* ignore */
|
|
179
195
|
}
|
|
180
196
|
}
|
|
181
|
-
if (appDirs.length === 0)
|
|
197
|
+
if (appDirs.length === 0) {
|
|
198
|
+
await this.mountCronJobs(root);
|
|
182
199
|
return;
|
|
183
|
-
|
|
184
|
-
;
|
|
200
|
+
}
|
|
201
|
+
this.apiDir = appDirs[0]; // keep property name for backwards compatibility
|
|
185
202
|
this._allApiDirs = appDirs;
|
|
186
203
|
for (const appDir of appDirs) {
|
|
187
204
|
await this.mountErrorHandler(appDir);
|
|
188
205
|
const routes = await this.scanDirectory(appDir);
|
|
189
206
|
for (const { filePath, routePath } of routes) {
|
|
190
|
-
|
|
191
|
-
filePath.endsWith('boundary.js') ||
|
|
192
|
-
/\.boundary\.[jt]s$/.test(filePath);
|
|
193
|
-
if (isBoundaryFile) {
|
|
207
|
+
if ((0, scanner_1.isBoundaryFile)(filePath)) {
|
|
194
208
|
this.hasBoundaries = true;
|
|
195
209
|
}
|
|
196
|
-
|
|
197
|
-
filePath.endsWith('route.js') ||
|
|
198
|
-
/\.route\.[jt]s$/.test(filePath);
|
|
199
|
-
if (isRouteFile) {
|
|
210
|
+
if ((0, scanner_1.isRouteFile)(filePath)) {
|
|
200
211
|
const normalized = path.resolve(filePath);
|
|
201
212
|
this.routeMap.set(normalized, routePath);
|
|
202
213
|
if (isDev) {
|
|
@@ -242,6 +253,7 @@ class RouteScanner {
|
|
|
242
253
|
}
|
|
243
254
|
}
|
|
244
255
|
}
|
|
256
|
+
await this.mountCronJobs(root);
|
|
245
257
|
// ─── Eager Paradigm Validation ──────────────────────────────────────────────
|
|
246
258
|
// Even in dev mode (lazy routes), pre-scan all route files at startup to
|
|
247
259
|
// detect mixed paradigms (OOP + functional) and throw a fatal error immediately.
|
|
@@ -293,6 +305,14 @@ class RouteScanner {
|
|
|
293
305
|
if (!this.hasBoundaries && !isProd) {
|
|
294
306
|
this.app.log.warn('No boundary.ts found — applying default security headers.');
|
|
295
307
|
}
|
|
308
|
+
const docsConfig = this.app.options.docs || this.app.options.swagger;
|
|
309
|
+
if (docsConfig &&
|
|
310
|
+
(typeof docsConfig === 'boolean'
|
|
311
|
+
? docsConfig
|
|
312
|
+
: docsConfig.enabled !== false)) {
|
|
313
|
+
const { mountDocumentation } = await Promise.resolve().then(() => __importStar(require('../swagger/mounter.js')));
|
|
314
|
+
mountDocumentation(this.app, typeof docsConfig === 'object' ? docsConfig : {});
|
|
315
|
+
}
|
|
296
316
|
}
|
|
297
317
|
// ─── Route File Mounting (shared by autoMount and HotReloader) ──────────────
|
|
298
318
|
async mountRouteFile(filePath, routePath) {
|
|
@@ -313,483 +333,13 @@ class RouteScanner {
|
|
|
313
333
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
314
334
|
mod = require(filePath);
|
|
315
335
|
}
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const apiDir = this.apiDir || dirname;
|
|
320
|
-
const normDirname = path.resolve(dirname).replace(/\\/g, '/').toLowerCase();
|
|
321
|
-
const normApiDir = path.resolve(apiDir).replace(/\\/g, '/').toLowerCase();
|
|
322
|
-
const segments = normDirname.startsWith(normApiDir)
|
|
323
|
-
? normDirname.slice(normApiDir.length).split('/').filter(Boolean)
|
|
324
|
-
: [];
|
|
325
|
-
const allMiddlewares = [];
|
|
326
|
-
const allFilters = [];
|
|
327
|
-
const allGuards = [];
|
|
328
|
-
const allInterceptors = [];
|
|
329
|
-
let allMetadata = {};
|
|
330
|
-
let allCors = undefined;
|
|
331
|
-
let allHeaders = {};
|
|
332
|
-
const activeBoundaries = [];
|
|
333
|
-
const dirsToCheck = [apiDir];
|
|
334
|
-
let tempDir = apiDir;
|
|
335
|
-
for (const segment of segments) {
|
|
336
|
-
tempDir = path.join(tempDir, segment);
|
|
337
|
-
dirsToCheck.push(tempDir);
|
|
338
|
-
}
|
|
339
|
-
for (const dir of dirsToCheck) {
|
|
340
|
-
try {
|
|
341
|
-
// Detect deprecated gateway.ts / gateway.js
|
|
342
|
-
const gatewayPathTs = path.join(dir, 'gateway.ts');
|
|
343
|
-
const gatewayPathJs = path.join(dir, 'gateway.js');
|
|
344
|
-
if ((await fs.stat(gatewayPathTs).catch(() => null)) ||
|
|
345
|
-
(await fs.stat(gatewayPathJs).catch(() => null))) {
|
|
346
|
-
this.app.log.warn(`Found deprecated 'gateway' file in '${dir}'. Gateways have been renamed to 'boundary.ts' in ExisJS v0.7+. Please rename it to boundary.ts.`);
|
|
347
|
-
}
|
|
348
|
-
const boundaryPathTs = path.join(dir, 'boundary.ts');
|
|
349
|
-
const boundaryPathJs = path.join(dir, 'boundary.js');
|
|
350
|
-
let targetBoundary = '';
|
|
351
|
-
if (await fs.stat(boundaryPathTs).catch(() => null))
|
|
352
|
-
targetBoundary = boundaryPathTs;
|
|
353
|
-
else if (await fs.stat(boundaryPathJs).catch(() => null))
|
|
354
|
-
targetBoundary = boundaryPathJs;
|
|
355
|
-
else {
|
|
356
|
-
// Check for named boundaries like user.boundary.ts
|
|
357
|
-
const dirFiles = await fs.readdir(dir).catch(() => []);
|
|
358
|
-
const namedBoundary = dirFiles.find((f) => /\.boundary\.[jt]s$/.test(f));
|
|
359
|
-
if (namedBoundary) {
|
|
360
|
-
targetBoundary = path.join(dir, namedBoundary);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
if (targetBoundary) {
|
|
364
|
-
activeBoundaries.push(targetBoundary);
|
|
365
|
-
this.hasBoundaries = true;
|
|
366
|
-
const boundaryUrl = process.env.VITEST || process.env.NODE_ENV === 'test'
|
|
367
|
-
? (0, node_url_1.pathToFileURL)(targetBoundary).href
|
|
368
|
-
: (0, node_url_1.pathToFileURL)(targetBoundary).href + '?t=' + Date.now();
|
|
369
|
-
let boundaryMod;
|
|
370
|
-
try {
|
|
371
|
-
if (process.env.VITEST || process.env.NODE_ENV === 'test') {
|
|
372
|
-
boundaryMod = await Promise.resolve(`${boundaryUrl}`).then(s => __importStar(require(s)));
|
|
373
|
-
}
|
|
374
|
-
else {
|
|
375
|
-
const dynamicImportBoundary = new Function('specifier', 'return import(specifier)');
|
|
376
|
-
boundaryMod = await dynamicImportBoundary(boundaryUrl);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
catch {
|
|
380
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
381
|
-
boundaryMod = require(targetBoundary);
|
|
382
|
-
}
|
|
383
|
-
// Extract config: check boundaryMod.config, boundaryMod.default, or boundaryMod itself
|
|
384
|
-
let boundaryConfig = null;
|
|
385
|
-
const BOUNDARY_CONFIG = Symbol.for('exisjs:boundary_config');
|
|
386
|
-
const boundaryDefault = boundaryMod && boundaryMod.default && boundaryMod.default.default
|
|
387
|
-
? boundaryMod.default.default
|
|
388
|
-
: boundaryMod && boundaryMod.default
|
|
389
|
-
? boundaryMod.default
|
|
390
|
-
: null;
|
|
391
|
-
if (boundaryMod &&
|
|
392
|
-
boundaryMod.config &&
|
|
393
|
-
typeof boundaryMod.config === 'object') {
|
|
394
|
-
boundaryConfig = boundaryMod.config;
|
|
395
|
-
}
|
|
396
|
-
else if (boundaryDefault &&
|
|
397
|
-
typeof boundaryDefault === 'function' &&
|
|
398
|
-
boundaryDefault.prototype &&
|
|
399
|
-
boundaryDefault.prototype[BOUNDARY_CONFIG]) {
|
|
400
|
-
// Class-based boundary with @Boundary
|
|
401
|
-
boundaryConfig = boundaryDefault.prototype[BOUNDARY_CONFIG];
|
|
402
|
-
}
|
|
403
|
-
else if (boundaryDefault && typeof boundaryDefault === 'object') {
|
|
404
|
-
boundaryConfig = boundaryDefault;
|
|
405
|
-
}
|
|
406
|
-
else if (boundaryMod && typeof boundaryMod === 'object') {
|
|
407
|
-
boundaryConfig = boundaryMod;
|
|
408
|
-
}
|
|
409
|
-
if (boundaryConfig &&
|
|
410
|
-
boundaryConfig.providers &&
|
|
411
|
-
Array.isArray(boundaryConfig.providers)) {
|
|
412
|
-
for (const p of boundaryConfig.providers) {
|
|
413
|
-
if (Array.isArray(p)) {
|
|
414
|
-
this.app.container.provide(p[0], p[1]);
|
|
415
|
-
}
|
|
416
|
-
else {
|
|
417
|
-
this.app.container.provide(p, p);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
const isExcluded = (reqPath, reqMethod) => {
|
|
422
|
-
if (!boundaryConfig || !boundaryConfig.exclude)
|
|
423
|
-
return false;
|
|
424
|
-
for (const rule of boundaryConfig.exclude) {
|
|
425
|
-
if (typeof rule === 'string') {
|
|
426
|
-
if (rule.endsWith('/*')) {
|
|
427
|
-
if (reqPath.startsWith(rule.slice(0, -2)))
|
|
428
|
-
return true;
|
|
429
|
-
}
|
|
430
|
-
else if (reqPath === rule)
|
|
431
|
-
return true;
|
|
432
|
-
}
|
|
433
|
-
else {
|
|
434
|
-
const pathMatch = rule.path.endsWith('/*')
|
|
435
|
-
? reqPath.startsWith(rule.path.slice(0, -2))
|
|
436
|
-
: reqPath === rule.path;
|
|
437
|
-
if (pathMatch) {
|
|
438
|
-
const methods = rule.methods || (rule.method ? [rule.method] : undefined);
|
|
439
|
-
if (!methods || methods.includes(reqMethod))
|
|
440
|
-
return true;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
return false;
|
|
445
|
-
};
|
|
446
|
-
// 1. Auto-detect named middleware functions: (req, res, next)
|
|
447
|
-
if (boundaryMod && typeof boundaryMod === 'object') {
|
|
448
|
-
for (const [exportKey, exportVal] of Object.entries(boundaryMod)) {
|
|
449
|
-
if (exportKey !== 'default' &&
|
|
450
|
-
exportKey !== 'config' &&
|
|
451
|
-
exportKey !== 'beforeHandle' &&
|
|
452
|
-
exportKey !== 'afterHandle' &&
|
|
453
|
-
typeof exportVal === 'function' &&
|
|
454
|
-
exportVal.length >= 3 // (req, res, next)
|
|
455
|
-
) {
|
|
456
|
-
const namedMiddleware = (req, res, next) => isExcluded(req.path, req.method)
|
|
457
|
-
? next()
|
|
458
|
-
: exportVal(req, res, next);
|
|
459
|
-
allMiddlewares.push(namedMiddleware);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
if (typeof boundaryMod.beforeHandle === 'function' &&
|
|
463
|
-
!boundaryConfig?.beforeHandle) {
|
|
464
|
-
if (!boundaryConfig)
|
|
465
|
-
boundaryConfig = {};
|
|
466
|
-
boundaryConfig.beforeHandle = boundaryMod.beforeHandle;
|
|
467
|
-
}
|
|
468
|
-
if (typeof boundaryMod.afterHandle === 'function' &&
|
|
469
|
-
!boundaryConfig?.afterHandle) {
|
|
470
|
-
if (!boundaryConfig)
|
|
471
|
-
boundaryConfig = {};
|
|
472
|
-
boundaryConfig.afterHandle = boundaryMod.afterHandle;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
// 2. Auto-detect pipeline wrapper function: export default async function(ctx, next)
|
|
476
|
-
// or class method named `handle(ctx, next)`
|
|
477
|
-
let wrapperFn = null;
|
|
478
|
-
if (boundaryDefault &&
|
|
479
|
-
typeof boundaryDefault === 'function' &&
|
|
480
|
-
boundaryDefault.length === 2 &&
|
|
481
|
-
!boundaryDefault.prototype?.[BOUNDARY_CONFIG]) {
|
|
482
|
-
wrapperFn = boundaryDefault;
|
|
483
|
-
}
|
|
484
|
-
else if (boundaryDefault &&
|
|
485
|
-
typeof boundaryDefault === 'function' &&
|
|
486
|
-
boundaryDefault.prototype &&
|
|
487
|
-
boundaryDefault.prototype[BOUNDARY_CONFIG]) {
|
|
488
|
-
const proto = boundaryDefault.prototype;
|
|
489
|
-
if (typeof proto.handle === 'function') {
|
|
490
|
-
const boundaryInstance = new boundaryDefault();
|
|
491
|
-
wrapperFn = proto.handle.bind(boundaryInstance);
|
|
492
|
-
}
|
|
493
|
-
// Also inspect method middlewares on class boundary
|
|
494
|
-
for (const prop of Object.getOwnPropertyNames(proto)) {
|
|
495
|
-
if (prop !== 'constructor' &&
|
|
496
|
-
prop !== 'handle' &&
|
|
497
|
-
prop !== 'beforeHandle' &&
|
|
498
|
-
prop !== 'afterHandle') {
|
|
499
|
-
const methodVal = proto[prop];
|
|
500
|
-
if (typeof methodVal === 'function' && methodVal.length >= 3) {
|
|
501
|
-
const boundaryInstance = new boundaryDefault();
|
|
502
|
-
const namedMiddleware = (req, res, next) => isExcluded(req.path, req.method)
|
|
503
|
-
? next()
|
|
504
|
-
: methodVal.call(boundaryInstance, req, res, next);
|
|
505
|
-
allMiddlewares.push(namedMiddleware);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
if (typeof proto.beforeHandle === 'function' &&
|
|
510
|
-
!boundaryConfig?.beforeHandle) {
|
|
511
|
-
const boundaryInstance = new boundaryDefault();
|
|
512
|
-
if (!boundaryConfig)
|
|
513
|
-
boundaryConfig = {};
|
|
514
|
-
boundaryConfig.beforeHandle =
|
|
515
|
-
proto.beforeHandle.bind(boundaryInstance);
|
|
516
|
-
}
|
|
517
|
-
if (typeof proto.afterHandle === 'function' &&
|
|
518
|
-
!boundaryConfig?.afterHandle) {
|
|
519
|
-
const boundaryInstance = new boundaryDefault();
|
|
520
|
-
if (!boundaryConfig)
|
|
521
|
-
boundaryConfig = {};
|
|
522
|
-
boundaryConfig.afterHandle =
|
|
523
|
-
proto.afterHandle.bind(boundaryInstance);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
if (wrapperFn) {
|
|
527
|
-
const capturedWrapper = wrapperFn;
|
|
528
|
-
const wrapperMiddleware = (req, res, next) => {
|
|
529
|
-
if (isExcluded(req.path, req.method))
|
|
530
|
-
return next();
|
|
531
|
-
let nextCalled = false;
|
|
532
|
-
let nextResolve;
|
|
533
|
-
let _nextReject;
|
|
534
|
-
const nextPromise = new Promise((resolve, reject) => {
|
|
535
|
-
nextResolve = resolve;
|
|
536
|
-
_nextReject = reject;
|
|
537
|
-
});
|
|
538
|
-
// Intercept response methods to catch handler return values
|
|
539
|
-
const originalJson = res.json.bind(res);
|
|
540
|
-
const originalSend = res.send.bind(res);
|
|
541
|
-
let handledByRoute = false;
|
|
542
|
-
res.json = function (body) {
|
|
543
|
-
if (!handledByRoute) {
|
|
544
|
-
handledByRoute = true;
|
|
545
|
-
nextResolve(body);
|
|
546
|
-
}
|
|
547
|
-
else {
|
|
548
|
-
return originalJson(body);
|
|
549
|
-
}
|
|
550
|
-
return this;
|
|
551
|
-
};
|
|
552
|
-
res.send = function (body) {
|
|
553
|
-
if (!handledByRoute) {
|
|
554
|
-
handledByRoute = true;
|
|
555
|
-
nextResolve(body);
|
|
556
|
-
}
|
|
557
|
-
else {
|
|
558
|
-
return originalSend(body);
|
|
559
|
-
}
|
|
560
|
-
return this;
|
|
561
|
-
};
|
|
562
|
-
const ctx = {
|
|
563
|
-
req,
|
|
564
|
-
res,
|
|
565
|
-
app: this.app,
|
|
566
|
-
resolve: (token) => this.app.resolve(token, req._diCache),
|
|
567
|
-
};
|
|
568
|
-
capturedWrapper(ctx, async () => {
|
|
569
|
-
if (!nextCalled) {
|
|
570
|
-
nextCalled = true;
|
|
571
|
-
next();
|
|
572
|
-
}
|
|
573
|
-
return nextPromise;
|
|
574
|
-
})
|
|
575
|
-
.then((wrapperResult) => {
|
|
576
|
-
if (wrapperResult !== undefined && !res.headersSent) {
|
|
577
|
-
if (typeof wrapperResult === 'object' &&
|
|
578
|
-
wrapperResult !== null) {
|
|
579
|
-
originalJson(wrapperResult);
|
|
580
|
-
}
|
|
581
|
-
else {
|
|
582
|
-
originalSend(String(wrapperResult));
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
})
|
|
586
|
-
.catch((wrapperErr) => {
|
|
587
|
-
next(wrapperErr);
|
|
588
|
-
});
|
|
589
|
-
};
|
|
590
|
-
allMiddlewares.push(wrapperMiddleware);
|
|
591
|
-
}
|
|
592
|
-
if (boundaryConfig) {
|
|
593
|
-
if (boundaryConfig.blockProbes ||
|
|
594
|
-
boundaryConfig.blockSuspiciousProbes) {
|
|
595
|
-
const probeOpts = typeof boundaryConfig.blockProbes === 'object'
|
|
596
|
-
? boundaryConfig.blockProbes
|
|
597
|
-
: typeof boundaryConfig.blockSuspiciousProbes === 'object'
|
|
598
|
-
? boundaryConfig.blockSuspiciousProbes
|
|
599
|
-
: {};
|
|
600
|
-
const { blockSuspiciousProbes } = await Promise.resolve().then(() => __importStar(require('../middleware/security')));
|
|
601
|
-
allMiddlewares.push(blockSuspiciousProbes(probeOpts));
|
|
602
|
-
}
|
|
603
|
-
const bMiddleware = boundaryConfig.middleware || boundaryConfig.middlewares;
|
|
604
|
-
if (bMiddleware) {
|
|
605
|
-
const mList = Array.isArray(bMiddleware)
|
|
606
|
-
? bMiddleware
|
|
607
|
-
: [bMiddleware];
|
|
608
|
-
const wrapped = mList.map((m) => (req, res, next) => isExcluded(req.path, req.method) ? next() : m(req, res, next));
|
|
609
|
-
allMiddlewares.push(...wrapped);
|
|
610
|
-
}
|
|
611
|
-
if (boundaryConfig.filters) {
|
|
612
|
-
const wrapped = boundaryConfig.filters.map((f) => (err, req, res, next) => isExcluded(req.path, req.method)
|
|
613
|
-
? next(err)
|
|
614
|
-
: f.prototype && f.prototype.catch
|
|
615
|
-
? new f().catch(err, req, res, next)
|
|
616
|
-
: f(err, req, res, next));
|
|
617
|
-
allFilters.push(...wrapped);
|
|
618
|
-
}
|
|
619
|
-
if (boundaryConfig.guards) {
|
|
620
|
-
const wrapped = boundaryConfig.guards.map((g) => {
|
|
621
|
-
return async (req) => {
|
|
622
|
-
if (isExcluded(req.path, req.method))
|
|
623
|
-
return true;
|
|
624
|
-
return typeof g === 'function'
|
|
625
|
-
? g.prototype?.canActivate
|
|
626
|
-
? new g().canActivate(req)
|
|
627
|
-
: g(req)
|
|
628
|
-
: g.canActivate(req);
|
|
629
|
-
};
|
|
630
|
-
});
|
|
631
|
-
allGuards.push(...wrapped);
|
|
632
|
-
}
|
|
633
|
-
if (boundaryConfig.interceptors) {
|
|
634
|
-
const wrapped = boundaryConfig.interceptors.map((i) => {
|
|
635
|
-
return async (req, res) => {
|
|
636
|
-
if (isExcluded(req.path, req.method))
|
|
637
|
-
return;
|
|
638
|
-
return typeof i === 'function'
|
|
639
|
-
? i.prototype?.intercept
|
|
640
|
-
? new i().intercept(req, res)
|
|
641
|
-
: i(req, res)
|
|
642
|
-
: i.intercept(req, res);
|
|
643
|
-
};
|
|
644
|
-
});
|
|
645
|
-
allInterceptors.push(...wrapped);
|
|
646
|
-
}
|
|
647
|
-
if (boundaryConfig.beforeHandle || boundaryConfig.afterHandle) {
|
|
648
|
-
const beforeHooks = (Array.isArray(boundaryConfig.beforeHandle)
|
|
649
|
-
? boundaryConfig.beforeHandle
|
|
650
|
-
: boundaryConfig.beforeHandle
|
|
651
|
-
? [boundaryConfig.beforeHandle]
|
|
652
|
-
: []);
|
|
653
|
-
const afterHooks = (Array.isArray(boundaryConfig.afterHandle)
|
|
654
|
-
? boundaryConfig.afterHandle
|
|
655
|
-
: boundaryConfig.afterHandle
|
|
656
|
-
? [boundaryConfig.afterHandle]
|
|
657
|
-
: []);
|
|
658
|
-
const hookMiddleware = async (req, res, next) => {
|
|
659
|
-
if (isExcluded(req.path, req.method))
|
|
660
|
-
return next();
|
|
661
|
-
for (const hook of beforeHooks) {
|
|
662
|
-
try {
|
|
663
|
-
const result = await hook(req, res);
|
|
664
|
-
if (result === false) {
|
|
665
|
-
if (!res.headersSent) {
|
|
666
|
-
res.status(403).json({
|
|
667
|
-
success: false,
|
|
668
|
-
error: {
|
|
669
|
-
code: 'FORBIDDEN',
|
|
670
|
-
message: 'Forbidden by boundary guard',
|
|
671
|
-
},
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
if (res.headersSent)
|
|
677
|
-
return;
|
|
678
|
-
}
|
|
679
|
-
catch (err) {
|
|
680
|
-
return next(err);
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
if (afterHooks.length === 0) {
|
|
684
|
-
return next();
|
|
685
|
-
}
|
|
686
|
-
const originalJson = res.json.bind(res);
|
|
687
|
-
const originalSend = res.send.bind(res);
|
|
688
|
-
let handled = false;
|
|
689
|
-
res.json = function (body) {
|
|
690
|
-
if (handled)
|
|
691
|
-
return originalJson(body);
|
|
692
|
-
handled = true;
|
|
693
|
-
const runAfterHooks = async () => {
|
|
694
|
-
let currentData = body;
|
|
695
|
-
for (const afterHook of afterHooks) {
|
|
696
|
-
const transformed = await afterHook(req, res, currentData);
|
|
697
|
-
if (transformed !== undefined) {
|
|
698
|
-
currentData = transformed;
|
|
699
|
-
}
|
|
700
|
-
if (res.headersSent)
|
|
701
|
-
return;
|
|
702
|
-
}
|
|
703
|
-
originalJson(currentData);
|
|
704
|
-
};
|
|
705
|
-
runAfterHooks().catch((err) => {
|
|
706
|
-
next(err);
|
|
707
|
-
});
|
|
708
|
-
return this;
|
|
709
|
-
};
|
|
710
|
-
res.send = function (body) {
|
|
711
|
-
if (handled)
|
|
712
|
-
return originalSend(body);
|
|
713
|
-
handled = true;
|
|
714
|
-
const runAfterHooks = async () => {
|
|
715
|
-
let currentData = body;
|
|
716
|
-
for (const afterHook of afterHooks) {
|
|
717
|
-
const transformed = await afterHook(req, res, currentData);
|
|
718
|
-
if (transformed !== undefined) {
|
|
719
|
-
currentData = transformed;
|
|
720
|
-
}
|
|
721
|
-
if (res.headersSent)
|
|
722
|
-
return;
|
|
723
|
-
}
|
|
724
|
-
originalSend(currentData);
|
|
725
|
-
};
|
|
726
|
-
runAfterHooks().catch((err) => {
|
|
727
|
-
next(err);
|
|
728
|
-
});
|
|
729
|
-
return this;
|
|
730
|
-
};
|
|
731
|
-
next();
|
|
732
|
-
};
|
|
733
|
-
allMiddlewares.push(hookMiddleware);
|
|
734
|
-
}
|
|
735
|
-
if (boundaryConfig.timeout !== undefined) {
|
|
736
|
-
const baseTimeout = boundaryConfig.timeout;
|
|
737
|
-
const timeoutMiddleware = (req, res, next) => {
|
|
738
|
-
if (isExcluded(req.path, req.method))
|
|
739
|
-
return next();
|
|
740
|
-
const ms = typeof baseTimeout === 'function'
|
|
741
|
-
? baseTimeout(req)
|
|
742
|
-
: baseTimeout;
|
|
743
|
-
if (ms) {
|
|
744
|
-
req.timeoutTimer = setTimeout(() => {
|
|
745
|
-
if (!res.headersSent) {
|
|
746
|
-
res.status(408).json({
|
|
747
|
-
success: false,
|
|
748
|
-
error: {
|
|
749
|
-
code: 'REQUEST_TIMEOUT',
|
|
750
|
-
message: 'Request Timeout',
|
|
751
|
-
},
|
|
752
|
-
});
|
|
753
|
-
}
|
|
754
|
-
}, ms);
|
|
755
|
-
res.raw.on('finish', () => clearTimeout(req.timeoutTimer));
|
|
756
|
-
}
|
|
757
|
-
next();
|
|
758
|
-
};
|
|
759
|
-
allMiddlewares.push(timeoutMiddleware);
|
|
760
|
-
}
|
|
761
|
-
if (boundaryConfig.metadata) {
|
|
762
|
-
allMetadata = { ...allMetadata, ...boundaryConfig.metadata };
|
|
763
|
-
}
|
|
764
|
-
if (boundaryConfig.cors !== undefined)
|
|
765
|
-
allCors = boundaryConfig.cors;
|
|
766
|
-
if (boundaryConfig.headers)
|
|
767
|
-
allHeaders = { ...allHeaders, ...boundaryConfig.headers };
|
|
768
|
-
if (boundaryConfig.plugins) {
|
|
769
|
-
for (const plugin of boundaryConfig.plugins) {
|
|
770
|
-
await this.app.pluginManager.register(plugin);
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
if (boundaryConfig.imports) {
|
|
774
|
-
for (const mod of boundaryConfig.imports) {
|
|
775
|
-
const name = 'plugin' in mod ? mod.plugin.name : mod.name;
|
|
776
|
-
if (!this.app.pluginManager.hasPlugin(name)) {
|
|
777
|
-
await this.app.pluginManager.register(mod);
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
if (boundaryConfig.providers) {
|
|
782
|
-
for (const [token, providerConfig] of boundaryConfig.providers) {
|
|
783
|
-
this.app.container.provide(token, providerConfig);
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
catch {
|
|
790
|
-
/* ignore */
|
|
791
|
-
}
|
|
336
|
+
const { activeBoundaries, boundaryMiddlewares, boundaryFilters, boundaryGuards, boundaryInterceptors, boundaryMetadata, boundaryCors, boundaryHeaders, hasBoundaries, } = await (0, scanner_1.loadActiveBoundaries)(filePath, this.apiDir || path.dirname(filePath), this.app);
|
|
337
|
+
if (hasBoundaries) {
|
|
338
|
+
this.hasBoundaries = true;
|
|
792
339
|
}
|
|
340
|
+
const allMiddlewares = [...boundaryMiddlewares];
|
|
341
|
+
let allCors = boundaryCors;
|
|
342
|
+
let allHeaders = { ...boundaryHeaders };
|
|
793
343
|
const currentMod = mod && mod.default && mod.default.default
|
|
794
344
|
? mod.default.default
|
|
795
345
|
: mod && mod.default
|
|
@@ -806,10 +356,12 @@ class RouteScanner {
|
|
|
806
356
|
else if (allCors === undefined && this.app.options.cors !== undefined)
|
|
807
357
|
allCors = this.app.options.cors;
|
|
808
358
|
const rMiddleware = routeConfig.middleware || routeConfig.middlewares;
|
|
809
|
-
if (rMiddleware)
|
|
359
|
+
if (rMiddleware) {
|
|
810
360
|
allMiddlewares.push(...(Array.isArray(rMiddleware) ? rMiddleware : [rMiddleware]));
|
|
811
|
-
|
|
361
|
+
}
|
|
362
|
+
if (routeConfig.headers) {
|
|
812
363
|
allHeaders = { ...allHeaders, ...routeConfig.headers };
|
|
364
|
+
}
|
|
813
365
|
if (routeConfig.plugins) {
|
|
814
366
|
for (const plugin of routeConfig.plugins) {
|
|
815
367
|
await this.app.pluginManager.register(plugin);
|
|
@@ -817,7 +369,7 @@ class RouteScanner {
|
|
|
817
369
|
}
|
|
818
370
|
// Pass boundary features to controllers via metadata if necessary
|
|
819
371
|
const combinedFilters = [
|
|
820
|
-
...
|
|
372
|
+
...boundaryFilters,
|
|
821
373
|
...(routeConfig.filters
|
|
822
374
|
? Array.isArray(routeConfig.filters)
|
|
823
375
|
? routeConfig.filters
|
|
@@ -825,7 +377,7 @@ class RouteScanner {
|
|
|
825
377
|
: []),
|
|
826
378
|
];
|
|
827
379
|
const combinedGuards = [
|
|
828
|
-
...
|
|
380
|
+
...boundaryGuards,
|
|
829
381
|
...(routeConfig.guards
|
|
830
382
|
? Array.isArray(routeConfig.guards)
|
|
831
383
|
? routeConfig.guards
|
|
@@ -833,7 +385,7 @@ class RouteScanner {
|
|
|
833
385
|
: []),
|
|
834
386
|
];
|
|
835
387
|
const combinedInterceptors = [
|
|
836
|
-
...
|
|
388
|
+
...boundaryInterceptors,
|
|
837
389
|
...(routeConfig.interceptors
|
|
838
390
|
? Array.isArray(routeConfig.interceptors)
|
|
839
391
|
? routeConfig.interceptors
|
|
@@ -855,7 +407,7 @@ class RouteScanner {
|
|
|
855
407
|
if (!mod.default.metadata)
|
|
856
408
|
mod.default.metadata = {};
|
|
857
409
|
Object.assign(mod.default.metadata, {
|
|
858
|
-
...
|
|
410
|
+
...boundaryMetadata,
|
|
859
411
|
...(routeConfig.metadata || {}),
|
|
860
412
|
});
|
|
861
413
|
}
|
|
@@ -865,13 +417,10 @@ class RouteScanner {
|
|
|
865
417
|
}
|
|
866
418
|
const prefixMiddlewares = [];
|
|
867
419
|
if (this.app.options.debugRouting) {
|
|
868
|
-
const
|
|
869
|
-
const relFile = pathLib
|
|
870
|
-
.relative(process.cwd(), filePath)
|
|
871
|
-
.replace(/\\/g, '/');
|
|
420
|
+
const relFile = path.relative(process.cwd(), filePath).replace(/\\/g, '/');
|
|
872
421
|
const boundaryStr = activeBoundaries.length > 0
|
|
873
422
|
? activeBoundaries
|
|
874
|
-
.map((g) =>
|
|
423
|
+
.map((g) => path.relative(process.cwd(), g).replace(/\\/g, '/'))
|
|
875
424
|
.join(' -> ') + ' -> '
|
|
876
425
|
: '';
|
|
877
426
|
prefixMiddlewares.push((req, res, next) => {
|
|
@@ -934,224 +483,7 @@ class RouteScanner {
|
|
|
934
483
|
}
|
|
935
484
|
}
|
|
936
485
|
compileFunctionalController(config) {
|
|
937
|
-
|
|
938
|
-
const fileMiddleware = [];
|
|
939
|
-
if (config.cors) {
|
|
940
|
-
fileMiddleware.push(config.cors === true ? (0, middleware_1.cors)({}) : (0, middleware_1.cors)(config.cors));
|
|
941
|
-
}
|
|
942
|
-
const fileMiddlewareConfig = config.middleware || config.middlewares;
|
|
943
|
-
if (fileMiddlewareConfig) {
|
|
944
|
-
fileMiddleware.push(...(Array.isArray(fileMiddlewareConfig)
|
|
945
|
-
? fileMiddlewareConfig
|
|
946
|
-
: [fileMiddlewareConfig]));
|
|
947
|
-
}
|
|
948
|
-
const { onError, onResponse } = config;
|
|
949
|
-
for (const [key, routeConfig] of Object.entries(config)) {
|
|
950
|
-
if ([
|
|
951
|
-
'cors',
|
|
952
|
-
'middleware',
|
|
953
|
-
'middlewares',
|
|
954
|
-
'onError',
|
|
955
|
-
'onResponse',
|
|
956
|
-
'__isController',
|
|
957
|
-
].includes(key))
|
|
958
|
-
continue;
|
|
959
|
-
const rc = routeConfig;
|
|
960
|
-
if (!rc || !rc.method || !rc.path || !rc.handle)
|
|
961
|
-
continue;
|
|
962
|
-
const routeMiddlewares = [...fileMiddleware];
|
|
963
|
-
if (rc.cors) {
|
|
964
|
-
routeMiddlewares.push(rc.cors === true ? (0, middleware_1.cors)({}) : (0, middleware_1.cors)(rc.cors));
|
|
965
|
-
}
|
|
966
|
-
const rcMiddleware = rc.middleware || rc.middlewares;
|
|
967
|
-
if (rcMiddleware) {
|
|
968
|
-
routeMiddlewares.push(...(Array.isArray(rcMiddleware) ? rcMiddleware : [rcMiddleware]));
|
|
969
|
-
}
|
|
970
|
-
// ─── SUPER HANDLER WRAPPER ───
|
|
971
|
-
const superHandler = async (req, res, next) => {
|
|
972
|
-
if (onResponse) {
|
|
973
|
-
res.raw.on('finish', () => onResponse(req, res));
|
|
974
|
-
}
|
|
975
|
-
try {
|
|
976
|
-
// Dynamic Route Timeout Override
|
|
977
|
-
const routeTimeoutVal = rc.timeoutMs !== undefined ? rc.timeoutMs : rc.timeout;
|
|
978
|
-
if (routeTimeoutVal !== undefined &&
|
|
979
|
-
typeof req.setTimeout === 'function') {
|
|
980
|
-
const ms = typeof routeTimeoutVal === 'number'
|
|
981
|
-
? routeTimeoutVal
|
|
982
|
-
: routeTimeoutVal?.ms;
|
|
983
|
-
if (typeof ms === 'number') {
|
|
984
|
-
if (ms <= 0)
|
|
985
|
-
req.clearTimeout?.();
|
|
986
|
-
else
|
|
987
|
-
req.setTimeout(ms);
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
// 0. Enforce Route Permissions (Role Authorization)
|
|
991
|
-
if (rc.permissions && rc.permissions.length > 0) {
|
|
992
|
-
if (!req.user) {
|
|
993
|
-
res.status(401).json({
|
|
994
|
-
success: false,
|
|
995
|
-
error: {
|
|
996
|
-
code: 'UNAUTHORIZED',
|
|
997
|
-
message: 'Unauthorized: User not found on request',
|
|
998
|
-
},
|
|
999
|
-
});
|
|
1000
|
-
return;
|
|
1001
|
-
}
|
|
1002
|
-
const userPerms = req.user.permissions || req.user.roles || req.user.role || [];
|
|
1003
|
-
const permsArray = Array.isArray(userPerms)
|
|
1004
|
-
? userPerms
|
|
1005
|
-
: [userPerms];
|
|
1006
|
-
const hasPerm = rc.permissions.every((p) => permsArray.includes(p));
|
|
1007
|
-
if (!hasPerm) {
|
|
1008
|
-
res.status(403).json({
|
|
1009
|
-
success: false,
|
|
1010
|
-
error: {
|
|
1011
|
-
code: 'FORBIDDEN',
|
|
1012
|
-
message: 'Forbidden: Insufficient permissions',
|
|
1013
|
-
},
|
|
1014
|
-
});
|
|
1015
|
-
return;
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
// 1. Run Guards
|
|
1019
|
-
const routeGuards = [...(config.guards || []), ...(rc.guards || [])];
|
|
1020
|
-
for (const guard of routeGuards) {
|
|
1021
|
-
const allowed = await (typeof guard === 'function'
|
|
1022
|
-
? guard.prototype?.canActivate
|
|
1023
|
-
? new guard().canActivate(req)
|
|
1024
|
-
: guard(req)
|
|
1025
|
-
: guard.canActivate(req));
|
|
1026
|
-
if (!allowed) {
|
|
1027
|
-
if (res && !res.headersSent) {
|
|
1028
|
-
res.status(403).json({
|
|
1029
|
-
success: false,
|
|
1030
|
-
error: { code: 'FORBIDDEN', message: 'Forbidden by Guard' },
|
|
1031
|
-
});
|
|
1032
|
-
}
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
// Build Context
|
|
1037
|
-
const ctx = {
|
|
1038
|
-
body: req.body,
|
|
1039
|
-
query: req.query,
|
|
1040
|
-
params: req.params,
|
|
1041
|
-
headers: req.headers,
|
|
1042
|
-
file: req.file,
|
|
1043
|
-
files: req.files,
|
|
1044
|
-
fields: req.body,
|
|
1045
|
-
req,
|
|
1046
|
-
res,
|
|
1047
|
-
app: this.app,
|
|
1048
|
-
resolve: (token) => this.app.resolve(token, req._diCache),
|
|
1049
|
-
socket: req.ws,
|
|
1050
|
-
state: context_1.executionContext.getStore()?.state || {},
|
|
1051
|
-
};
|
|
1052
|
-
if (req.user !== undefined)
|
|
1053
|
-
ctx.user = req.user;
|
|
1054
|
-
if (req.session !== undefined)
|
|
1055
|
-
ctx.session = req.session;
|
|
1056
|
-
const result = await rc.handle(ctx);
|
|
1057
|
-
// 2. Run Interceptors
|
|
1058
|
-
const routeInterceptors = [
|
|
1059
|
-
...(config.interceptors || []),
|
|
1060
|
-
...(rc.interceptors || []),
|
|
1061
|
-
];
|
|
1062
|
-
for (const interceptor of routeInterceptors) {
|
|
1063
|
-
await (typeof interceptor === 'function'
|
|
1064
|
-
? interceptor.prototype?.intercept
|
|
1065
|
-
? new interceptor().intercept(req, res)
|
|
1066
|
-
: interceptor(req, res)
|
|
1067
|
-
: interceptor.intercept(req, res));
|
|
1068
|
-
}
|
|
1069
|
-
if (result !== undefined && !res.headersSent) {
|
|
1070
|
-
if (typeof result === 'object' && result !== null) {
|
|
1071
|
-
res.json(result);
|
|
1072
|
-
}
|
|
1073
|
-
else {
|
|
1074
|
-
res.send(String(result));
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
catch (err) {
|
|
1079
|
-
if (onError) {
|
|
1080
|
-
try {
|
|
1081
|
-
await onError(err, req, res);
|
|
1082
|
-
}
|
|
1083
|
-
catch (hookErr) {
|
|
1084
|
-
next(hookErr);
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
else {
|
|
1088
|
-
next(err); // Hands off to ExisJS global error handler (which handles HttpErrors automatically!)
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
};
|
|
1092
|
-
// Mount to router
|
|
1093
|
-
const method = rc.method.toLowerCase();
|
|
1094
|
-
const schema = {};
|
|
1095
|
-
const isValidatorOrPipe = (v) => v.parse ||
|
|
1096
|
-
v.transform ||
|
|
1097
|
-
(typeof v === 'function' && v.prototype?.transform);
|
|
1098
|
-
if (rc.body) {
|
|
1099
|
-
schema.body = isValidatorOrPipe(rc.body) ? rc.body : index_1.tex.object(rc.body);
|
|
1100
|
-
}
|
|
1101
|
-
if (rc.query) {
|
|
1102
|
-
schema.query = isValidatorOrPipe(rc.query)
|
|
1103
|
-
? rc.query
|
|
1104
|
-
: index_1.tex.object(rc.query);
|
|
1105
|
-
}
|
|
1106
|
-
if (rc.params) {
|
|
1107
|
-
schema.params = isValidatorOrPipe(rc.params)
|
|
1108
|
-
? rc.params
|
|
1109
|
-
: index_1.tex.object(rc.params);
|
|
1110
|
-
}
|
|
1111
|
-
if (rc.host) {
|
|
1112
|
-
schema.host = rc.host;
|
|
1113
|
-
}
|
|
1114
|
-
if (rc.timeoutMs !== undefined) {
|
|
1115
|
-
schema.timeoutMs = rc.timeoutMs;
|
|
1116
|
-
}
|
|
1117
|
-
if (rc.timeout !== undefined) {
|
|
1118
|
-
schema.timeout = rc.timeout;
|
|
1119
|
-
}
|
|
1120
|
-
if (rc.upload !== undefined) {
|
|
1121
|
-
schema.upload = rc.upload;
|
|
1122
|
-
}
|
|
1123
|
-
const combinedFilters = [
|
|
1124
|
-
...(config.filters
|
|
1125
|
-
? Array.isArray(config.filters)
|
|
1126
|
-
? config.filters
|
|
1127
|
-
: [config.filters]
|
|
1128
|
-
: []),
|
|
1129
|
-
...(rc.filters
|
|
1130
|
-
? Array.isArray(rc.filters)
|
|
1131
|
-
? rc.filters
|
|
1132
|
-
: [rc.filters]
|
|
1133
|
-
: []),
|
|
1134
|
-
];
|
|
1135
|
-
if (combinedFilters.length > 0) {
|
|
1136
|
-
schema.filters = combinedFilters;
|
|
1137
|
-
}
|
|
1138
|
-
const combinedMetadata = {
|
|
1139
|
-
...(config.metadata || {}),
|
|
1140
|
-
...(rc.metadata || {}),
|
|
1141
|
-
};
|
|
1142
|
-
if (Object.keys(combinedMetadata).length > 0) {
|
|
1143
|
-
schema.metadata = combinedMetadata;
|
|
1144
|
-
}
|
|
1145
|
-
if (Object.keys(schema).length > 0) {
|
|
1146
|
-
;
|
|
1147
|
-
router[method](rc.path, ...routeMiddlewares, schema, superHandler);
|
|
1148
|
-
}
|
|
1149
|
-
else {
|
|
1150
|
-
;
|
|
1151
|
-
router[method](rc.path, ...routeMiddlewares, superHandler);
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
return router;
|
|
486
|
+
return (0, scanner_1.compileFunctionalController)(config, this.app);
|
|
1155
487
|
}
|
|
1156
488
|
mountRouteWithSource(prefix, subRouter, sourceFile, prefixMiddlewares = []) {
|
|
1157
489
|
for (const route of subRouter.getRoutes()) {
|
|
@@ -1173,95 +505,20 @@ class RouteScanner {
|
|
|
1173
505
|
}
|
|
1174
506
|
return this;
|
|
1175
507
|
}
|
|
508
|
+
mountErrorHandlerFromModule(mod, errorFile = 'error.ts') {
|
|
509
|
+
(0, scanner_1.mountErrorHandlerFromModule)(mod, this.app, errorFile);
|
|
510
|
+
}
|
|
1176
511
|
async mountErrorHandler(dir) {
|
|
1177
|
-
|
|
1178
|
-
const path = await Promise.resolve().then(() => __importStar(require('node:path')));
|
|
1179
|
-
const candidateFiles = [
|
|
1180
|
-
path.join(dir, 'error.ts'),
|
|
1181
|
-
path.join(dir, 'error.js'),
|
|
1182
|
-
path.join(dir, 'error.mjs'),
|
|
1183
|
-
path.join(dir, 'error.cjs'),
|
|
1184
|
-
];
|
|
1185
|
-
let errorFile = null;
|
|
1186
|
-
for (const file of candidateFiles) {
|
|
1187
|
-
try {
|
|
1188
|
-
const stat = await fs.stat(file);
|
|
1189
|
-
if (stat.isFile()) {
|
|
1190
|
-
errorFile = file;
|
|
1191
|
-
break;
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
catch {
|
|
1195
|
-
// ignore
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
if (!errorFile)
|
|
1199
|
-
return;
|
|
1200
|
-
try {
|
|
1201
|
-
const url = process.env.VITEST || process.env.NODE_ENV === 'test'
|
|
1202
|
-
? (0, node_url_1.pathToFileURL)(errorFile).href
|
|
1203
|
-
: (0, node_url_1.pathToFileURL)(errorFile).href + '?t=' + Date.now();
|
|
1204
|
-
let mod;
|
|
1205
|
-
if (process.env.VITEST || process.env.NODE_ENV === 'test') {
|
|
1206
|
-
mod = await Promise.resolve(`${url}`).then(s => __importStar(require(s)));
|
|
1207
|
-
}
|
|
1208
|
-
else {
|
|
1209
|
-
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
1210
|
-
mod = await dynamicImport(url);
|
|
1211
|
-
}
|
|
1212
|
-
const unwrapped = mod && mod.default && mod.default.default
|
|
1213
|
-
? mod.default.default
|
|
1214
|
-
: mod && mod.default
|
|
1215
|
-
? mod.default
|
|
1216
|
-
: mod;
|
|
1217
|
-
const handler = mod.onError ||
|
|
1218
|
-
(unwrapped && unwrapped.onError) ||
|
|
1219
|
-
(typeof unwrapped === 'function' ? unwrapped : null) ||
|
|
1220
|
-
mod.errorHandler ||
|
|
1221
|
-
mod.handleError;
|
|
1222
|
-
if (typeof handler === 'function') {
|
|
1223
|
-
if (handler.length === 4) {
|
|
1224
|
-
this.app.use(handler);
|
|
1225
|
-
}
|
|
1226
|
-
else {
|
|
1227
|
-
this.app.onError(handler);
|
|
1228
|
-
}
|
|
1229
|
-
this.app.log.info({ file: errorFile }, 'Mounted global error handler from error.ts');
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
catch (err) {
|
|
1233
|
-
this.app.log.error({ err, file: errorFile }, `Failed to load error handler file: ${errorFile}`);
|
|
1234
|
-
}
|
|
512
|
+
await (0, scanner_1.mountErrorHandler)(dir, this.app);
|
|
1235
513
|
}
|
|
1236
514
|
async scanDirectory(dir, baseRoute = '/') {
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
let segment = entry.name;
|
|
1245
|
-
if (segment.startsWith('(') && segment.endsWith(')')) {
|
|
1246
|
-
const subResults = await this.scanDirectory(fullPath, baseRoute);
|
|
1247
|
-
results.push(...subResults);
|
|
1248
|
-
continue;
|
|
1249
|
-
}
|
|
1250
|
-
if (segment.startsWith('[...') && segment.endsWith(']')) {
|
|
1251
|
-
segment = '*' + segment.slice(4, -1);
|
|
1252
|
-
}
|
|
1253
|
-
else {
|
|
1254
|
-
segment = segment.replace(/\[(.*?)\]/g, ':$1');
|
|
1255
|
-
}
|
|
1256
|
-
const nextBase = baseRoute === '/' ? `/${segment}` : `${baseRoute}/${segment}`;
|
|
1257
|
-
const subResults = await this.scanDirectory(fullPath, nextBase);
|
|
1258
|
-
results.push(...subResults);
|
|
1259
|
-
}
|
|
1260
|
-
else {
|
|
1261
|
-
results.push({ filePath: fullPath, routePath: baseRoute });
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
return results;
|
|
515
|
+
return (0, scanner_1.scanDirectory)(dir, baseRoute);
|
|
516
|
+
}
|
|
517
|
+
async mountCronJobs(root) {
|
|
518
|
+
await (0, scanner_1.mountCronJobs)(root, this.app, this._allApiDirs);
|
|
519
|
+
}
|
|
520
|
+
mountCronJobsFromEntries(entries) {
|
|
521
|
+
(0, scanner_1.mountCronJobsFromEntries)(entries, this.app);
|
|
1265
522
|
}
|
|
1266
523
|
}
|
|
1267
524
|
exports.RouteScanner = RouteScanner;
|