exisjs 0.4.3 → 0.4.5
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 +43 -55
- package/dist/cli/commands/build.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 +76 -17
- package/dist/cli/manifest.js.map +1 -1
- package/dist/exports/sanitize.d.ts +2 -0
- package/dist/exports/sanitize.d.ts.map +1 -0
- package/dist/exports/sanitize.js +6 -0
- package/dist/exports/sanitize.js.map +1 -0
- package/dist/middleware/middleware.d.ts.map +1 -1
- package/dist/middleware/middleware.js +5 -0
- package/dist/middleware/middleware.js.map +1 -1
- package/dist/server/app.d.ts +51 -55
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +123 -2047
- package/dist/server/app.js.map +1 -1
- package/dist/server/bootstrapper.d.ts +24 -0
- package/dist/server/bootstrapper.d.ts.map +1 -0
- package/dist/server/bootstrapper.js +408 -0
- package/dist/server/bootstrapper.js.map +1 -0
- package/dist/server/controller-registrar.d.ts +8 -0
- package/dist/server/controller-registrar.d.ts.map +1 -0
- package/dist/server/controller-registrar.js +351 -0
- package/dist/server/controller-registrar.js.map +1 -0
- package/dist/server/plugin-manager.d.ts +26 -0
- package/dist/server/plugin-manager.d.ts.map +1 -0
- package/dist/server/plugin-manager.js +150 -0
- package/dist/server/plugin-manager.js.map +1 -0
- package/dist/server/queue-manager.d.ts +18 -0
- package/dist/server/queue-manager.d.ts.map +1 -0
- package/dist/server/queue-manager.js +54 -0
- package/dist/server/queue-manager.js.map +1 -0
- package/dist/server/request-handler.d.ts +25 -0
- package/dist/server/request-handler.d.ts.map +1 -0
- package/dist/server/request-handler.js +230 -0
- package/dist/server/request-handler.js.map +1 -0
- package/dist/server/route-scanner.d.ts +20 -0
- package/dist/server/route-scanner.d.ts.map +1 -0
- package/dist/server/route-scanner.js +892 -0
- package/dist/server/route-scanner.js.map +1 -0
- package/dist/server/ws-orchestrator.d.ts +15 -0
- package/dist/server/ws-orchestrator.d.ts.map +1 -0
- package/dist/server/ws-orchestrator.js +156 -0
- package/dist/server/ws-orchestrator.js.map +1 -0
- package/dist/utils/port.d.ts +2 -0
- package/dist/utils/port.d.ts.map +1 -0
- package/dist/utils/port.js +30 -0
- package/dist/utils/port.js.map +1 -0
- package/dist/utils/sanitize.d.ts +33 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +95 -0
- package/dist/utils/sanitize.js.map +1 -0
- package/dist/utils/validator.d.ts +2 -0
- package/dist/utils/validator.d.ts.map +1 -1
- package/dist/utils/validator.js +50 -17
- package/dist/utils/validator.js.map +1 -1
- package/package.json +8 -3
|
@@ -0,0 +1,892 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.RouteScanner = void 0;
|
|
37
|
+
const router_1 = require("../router/router");
|
|
38
|
+
const middleware_1 = require("../middleware/middleware");
|
|
39
|
+
const validator_1 = require("../utils/validator");
|
|
40
|
+
const node_url_1 = require("node:url");
|
|
41
|
+
const context_1 = require("./context");
|
|
42
|
+
const dev_error_overlay_1 = require("./dev-error-overlay");
|
|
43
|
+
class RouteScanner {
|
|
44
|
+
constructor(app) {
|
|
45
|
+
this.app = app;
|
|
46
|
+
this.lazyRoutes = new Map();
|
|
47
|
+
this.routeMap = new Map();
|
|
48
|
+
this.apiDir = null;
|
|
49
|
+
}
|
|
50
|
+
async loadAllRoutes() {
|
|
51
|
+
for (const [filePath, entry] of this.lazyRoutes.entries()) {
|
|
52
|
+
if (!entry.loaded) {
|
|
53
|
+
entry.loaded = true;
|
|
54
|
+
this.app.getRouter().removeRoutesBySource('lazy:' + filePath);
|
|
55
|
+
const routePath = this.routeMap.get(filePath);
|
|
56
|
+
if (routePath) {
|
|
57
|
+
try {
|
|
58
|
+
await this.mountRouteFile(filePath, routePath);
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
this.app.log.error({ err: e, file: filePath }, 'Failed to eager-load route');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async autoMountRoutes(root) {
|
|
68
|
+
const fs = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
69
|
+
const path = await Promise.resolve().then(() => __importStar(require('node:path')));
|
|
70
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
71
|
+
const isDev = this.app.options.env === 'development';
|
|
72
|
+
// Try to load the pre-built manifest first (O(1) boot)
|
|
73
|
+
const manifestPath = path.join(root, '.exis', 'routes-manifest.js');
|
|
74
|
+
try {
|
|
75
|
+
const stat = await fs.stat(manifestPath);
|
|
76
|
+
if (stat.isFile()) {
|
|
77
|
+
let url = (0, node_url_1.pathToFileURL)(manifestPath).href;
|
|
78
|
+
if (!isProd)
|
|
79
|
+
url += '?t=' + Date.now(); // cache bust for dev
|
|
80
|
+
let mod;
|
|
81
|
+
if (process.env.VITEST || process.env.NODE_ENV === 'test') {
|
|
82
|
+
mod = await Promise.resolve(`${url}`).then(s => __importStar(require(s)));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
86
|
+
mod = await dynamicImport(url);
|
|
87
|
+
}
|
|
88
|
+
const manifest = mod.manifest;
|
|
89
|
+
if (Array.isArray(manifest)) {
|
|
90
|
+
for (const entry of manifest) {
|
|
91
|
+
const { routePath, module: routeMod, filePath } = entry;
|
|
92
|
+
const normalizedPath = path.resolve(root, filePath);
|
|
93
|
+
this.routeMap.set(normalizedPath, routePath);
|
|
94
|
+
const CONTROLLER_PREFIX = Symbol.for('exisjs:controller_prefix');
|
|
95
|
+
const isClassController = (obj) => obj &&
|
|
96
|
+
obj.prototype &&
|
|
97
|
+
obj.prototype[CONTROLLER_PREFIX] !== undefined;
|
|
98
|
+
const unwrappedMod = routeMod.default && routeMod.default.default
|
|
99
|
+
? routeMod.default.default
|
|
100
|
+
: routeMod.default
|
|
101
|
+
? routeMod.default
|
|
102
|
+
: routeMod;
|
|
103
|
+
const functionalControllerObj = unwrappedMod && unwrappedMod.__isController ? unwrappedMod : null;
|
|
104
|
+
const routerInstance = routeMod.router || routeMod.default || routeMod;
|
|
105
|
+
if (functionalControllerObj) {
|
|
106
|
+
const router = this.compileFunctionalController(functionalControllerObj);
|
|
107
|
+
this.mountRouteWithSource(routePath, router, normalizedPath);
|
|
108
|
+
}
|
|
109
|
+
else if (isClassController(routerInstance)) {
|
|
110
|
+
this.app.registerControllers([routerInstance], routePath);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.mountRouteWithSource(routePath, routerInstance, normalizedPath);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Skip the filesystem scan completely!
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
if (err.code !== 'ENOENT') {
|
|
123
|
+
console.warn('exisjs: Failed to load routes manifest:', err.message);
|
|
124
|
+
}
|
|
125
|
+
// Fall back to scanning the filesystem
|
|
126
|
+
}
|
|
127
|
+
const searchDirs = isProd
|
|
128
|
+
? [
|
|
129
|
+
path.join(root, '.exis', 'server', 'src', 'http'),
|
|
130
|
+
path.join(root, '.exis', 'server', 'http'),
|
|
131
|
+
path.join(root, 'dist', 'src', 'http'),
|
|
132
|
+
path.join(root, 'dist', 'http'),
|
|
133
|
+
path.join(root, 'src', 'http'),
|
|
134
|
+
path.join(root, 'http'),
|
|
135
|
+
]
|
|
136
|
+
: [path.join(root, 'src', 'http'), path.join(root, 'http')];
|
|
137
|
+
const appDirs = [];
|
|
138
|
+
for (const dir of searchDirs) {
|
|
139
|
+
try {
|
|
140
|
+
const stat = await fs.stat(dir);
|
|
141
|
+
if (stat.isDirectory()) {
|
|
142
|
+
const baseName = path.basename(path.dirname(dir)) === 'src' ? 'src/http' : 'http';
|
|
143
|
+
const alreadyHasCompiledOrRawForThisBase = appDirs.some((existing) => {
|
|
144
|
+
const existingBase = path.basename(path.dirname(existing)) === 'src'
|
|
145
|
+
? 'src/http'
|
|
146
|
+
: 'http';
|
|
147
|
+
return existingBase === baseName;
|
|
148
|
+
});
|
|
149
|
+
if (!alreadyHasCompiledOrRawForThisBase) {
|
|
150
|
+
appDirs.push(dir);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
/* ignore */
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (appDirs.length === 0)
|
|
159
|
+
return;
|
|
160
|
+
this.apiDir = appDirs[0] // keep property name for backwards compatibility
|
|
161
|
+
;
|
|
162
|
+
this._allApiDirs = appDirs;
|
|
163
|
+
const cc = {
|
|
164
|
+
green: '\x1b[32m',
|
|
165
|
+
cyan: '\x1b[36m',
|
|
166
|
+
gray: '\x1b[90m',
|
|
167
|
+
reset: '\x1b[0m',
|
|
168
|
+
};
|
|
169
|
+
for (const appDir of appDirs) {
|
|
170
|
+
const routes = await this.scanDirectory(appDir);
|
|
171
|
+
for (const { filePath, routePath } of routes) {
|
|
172
|
+
if (filePath.endsWith('route.ts') || filePath.endsWith('route.js')) {
|
|
173
|
+
const normalized = path.resolve(filePath);
|
|
174
|
+
this.routeMap.set(normalized, routePath);
|
|
175
|
+
if (isDev) {
|
|
176
|
+
const lazyKey = normalized;
|
|
177
|
+
this.lazyRoutes.set(lazyKey, {
|
|
178
|
+
filePath: normalized,
|
|
179
|
+
loaded: false,
|
|
180
|
+
});
|
|
181
|
+
const lazyHandler = async (req, res, next) => {
|
|
182
|
+
const entry = this.lazyRoutes.get(lazyKey);
|
|
183
|
+
if (entry && !entry.loaded) {
|
|
184
|
+
entry.loaded = true;
|
|
185
|
+
this.app.getRouter().removeRoutesBySource('lazy:' + lazyKey);
|
|
186
|
+
try {
|
|
187
|
+
await this.mountRouteFile(normalized, routePath);
|
|
188
|
+
const relative = path
|
|
189
|
+
.relative(process.cwd(), normalized)
|
|
190
|
+
.replace(/\\/g, '/');
|
|
191
|
+
console.log(` ${cc.green}✓${cc.reset} Lazy loaded ${cc.cyan}${relative}${cc.reset}`);
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
(0, dev_error_overlay_1.formatDevError)(err, normalized);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
await this.app.getRouter().handle(req, res, next);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
next?.();
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
const lazyRouter = new router_1.Router();
|
|
204
|
+
lazyRouter.all(routePath, lazyHandler);
|
|
205
|
+
lazyRouter.all(routePath + '/*path', lazyHandler);
|
|
206
|
+
for (const r of lazyRouter.getRoutes()) {
|
|
207
|
+
r.sourceFile = 'lazy:' + lazyKey;
|
|
208
|
+
this.app.getRouter().addRawRoute(r);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
try {
|
|
213
|
+
await this.mountRouteFile(normalized, routePath);
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
this.app.log.error({ err, file: filePath }, `Failed to load route file: ${filePath}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// ─── Route File Mounting (shared by autoMount and HotReloader) ──────────────
|
|
224
|
+
// ─── Auto-Mount File-Based Jobs ──────────────────────────────────────────────
|
|
225
|
+
async autoMountJobs(root) {
|
|
226
|
+
if (!this.app._queueWorker)
|
|
227
|
+
return;
|
|
228
|
+
const fs = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
229
|
+
const path = await Promise.resolve().then(() => __importStar(require('node:path')));
|
|
230
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
231
|
+
const searchDirs = isProd
|
|
232
|
+
? [
|
|
233
|
+
path.join(root, '.exis', 'server', 'src', 'jobs'),
|
|
234
|
+
path.join(root, '.exis', 'server', 'jobs'),
|
|
235
|
+
path.join(root, 'dist', 'src', 'jobs'),
|
|
236
|
+
path.join(root, 'dist', 'jobs'),
|
|
237
|
+
path.join(root, 'src', 'jobs'),
|
|
238
|
+
path.join(root, 'jobs'),
|
|
239
|
+
]
|
|
240
|
+
: [path.join(root, 'src', 'jobs'), path.join(root, 'jobs')];
|
|
241
|
+
let jobsDir = '';
|
|
242
|
+
for (const dir of searchDirs) {
|
|
243
|
+
try {
|
|
244
|
+
const stat = await fs.stat(dir);
|
|
245
|
+
if (stat.isDirectory()) {
|
|
246
|
+
jobsDir = dir;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
/* ignore */
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (!jobsDir)
|
|
255
|
+
return;
|
|
256
|
+
try {
|
|
257
|
+
const entries = await fs.readdir(jobsDir, { withFileTypes: true });
|
|
258
|
+
for (const entry of entries) {
|
|
259
|
+
if (entry.isFile() &&
|
|
260
|
+
(entry.name.endsWith('.ts') || entry.name.endsWith('.js'))) {
|
|
261
|
+
const filePath = path.join(jobsDir, entry.name);
|
|
262
|
+
const name = entry.name.replace(/\.(ts|js)$/, '');
|
|
263
|
+
try {
|
|
264
|
+
// Load the definition directly (no compilation needed here just to get the schema)
|
|
265
|
+
const mod = await Promise.resolve(`${filePath}`).then(s => __importStar(require(s)));
|
|
266
|
+
const jobDef = mod.default || mod;
|
|
267
|
+
if (jobDef) {
|
|
268
|
+
const def = {
|
|
269
|
+
name: jobDef.name || name,
|
|
270
|
+
filePath, // Attach filePath for Thread Pool execution
|
|
271
|
+
cron: jobDef.cron,
|
|
272
|
+
schema: jobDef.schema,
|
|
273
|
+
defaultOptions: jobDef.defaultOptions,
|
|
274
|
+
};
|
|
275
|
+
this.app._queueWorker.registerJob(def);
|
|
276
|
+
if (this.app._cronScheduler && jobDef.cron) {
|
|
277
|
+
this.app._cronScheduler.registerJob(def);
|
|
278
|
+
}
|
|
279
|
+
this.app.log.info(`Registered background job: ${name}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
this.app.log.error(`Failed to load job file ${filePath}: ${err}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
// Ignore if jobs directory does not exist
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async mountRouteFile(filePath, routePath) {
|
|
293
|
+
let mod;
|
|
294
|
+
try {
|
|
295
|
+
const url = process.env.VITEST || process.env.NODE_ENV === 'test'
|
|
296
|
+
? (0, node_url_1.pathToFileURL)(filePath).href
|
|
297
|
+
: (0, node_url_1.pathToFileURL)(filePath).href + '?t=' + Date.now();
|
|
298
|
+
if (process.env.VITEST || process.env.NODE_ENV === 'test') {
|
|
299
|
+
mod = await Promise.resolve(`${url}`).then(s => __importStar(require(s)));
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
303
|
+
mod = await dynamicImport(url);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
308
|
+
mod = require(filePath);
|
|
309
|
+
}
|
|
310
|
+
const path = await Promise.resolve().then(() => __importStar(require('node:path')));
|
|
311
|
+
const fs = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
312
|
+
const dirname = path.dirname(filePath);
|
|
313
|
+
const apiDir = this.apiDir || dirname;
|
|
314
|
+
const normDirname = path.resolve(dirname).replace(/\\/g, '/').toLowerCase();
|
|
315
|
+
const normApiDir = path.resolve(apiDir).replace(/\\/g, '/').toLowerCase();
|
|
316
|
+
const segments = normDirname.startsWith(normApiDir)
|
|
317
|
+
? normDirname.slice(normApiDir.length).split('/').filter(Boolean)
|
|
318
|
+
: [];
|
|
319
|
+
const allMiddlewares = [];
|
|
320
|
+
const allFilters = [];
|
|
321
|
+
const allGuards = [];
|
|
322
|
+
const allInterceptors = [];
|
|
323
|
+
let allMetadata = {};
|
|
324
|
+
let allCors = undefined;
|
|
325
|
+
let allHeaders = {};
|
|
326
|
+
const activeGateways = [];
|
|
327
|
+
const dirsToCheck = [apiDir];
|
|
328
|
+
let tempDir = apiDir;
|
|
329
|
+
for (const segment of segments) {
|
|
330
|
+
tempDir = path.join(tempDir, segment);
|
|
331
|
+
dirsToCheck.push(tempDir);
|
|
332
|
+
}
|
|
333
|
+
for (const dir of dirsToCheck) {
|
|
334
|
+
try {
|
|
335
|
+
const gwPathJs = path.join(dir, 'gateway.js');
|
|
336
|
+
const gwPathTs = path.join(dir, 'gateway.ts');
|
|
337
|
+
let targetGw = '';
|
|
338
|
+
if (await fs.stat(gwPathTs).catch(() => null))
|
|
339
|
+
targetGw = gwPathTs;
|
|
340
|
+
else if (await fs.stat(gwPathJs).catch(() => null))
|
|
341
|
+
targetGw = gwPathJs;
|
|
342
|
+
if (targetGw) {
|
|
343
|
+
activeGateways.push(targetGw);
|
|
344
|
+
const gwUrl = process.env.VITEST || process.env.NODE_ENV === 'test'
|
|
345
|
+
? (0, node_url_1.pathToFileURL)(targetGw).href
|
|
346
|
+
: (0, node_url_1.pathToFileURL)(targetGw).href + '?t=' + Date.now();
|
|
347
|
+
let gwMod;
|
|
348
|
+
try {
|
|
349
|
+
if (process.env.VITEST || process.env.NODE_ENV === 'test') {
|
|
350
|
+
gwMod = await Promise.resolve(`${gwUrl}`).then(s => __importStar(require(s)));
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
const dynamicImportGw = new Function('specifier', 'return import(specifier)');
|
|
354
|
+
gwMod = await dynamicImportGw(gwUrl);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
359
|
+
gwMod = require(targetGw);
|
|
360
|
+
}
|
|
361
|
+
const gwConfig = gwMod && gwMod.default && gwMod.default.default
|
|
362
|
+
? gwMod.default.default
|
|
363
|
+
: gwMod && gwMod.default
|
|
364
|
+
? gwMod.default
|
|
365
|
+
: gwMod;
|
|
366
|
+
if (gwConfig) {
|
|
367
|
+
const isExcluded = (reqPath, reqMethod) => {
|
|
368
|
+
if (!gwConfig.exclude)
|
|
369
|
+
return false;
|
|
370
|
+
for (const rule of gwConfig.exclude) {
|
|
371
|
+
if (typeof rule === 'string') {
|
|
372
|
+
if (rule.endsWith('/*')) {
|
|
373
|
+
if (reqPath.startsWith(rule.slice(0, -2)))
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
else if (reqPath === rule)
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
const pathMatch = rule.path.endsWith('/*')
|
|
381
|
+
? reqPath.startsWith(rule.path.slice(0, -2))
|
|
382
|
+
: reqPath === rule.path;
|
|
383
|
+
if (pathMatch) {
|
|
384
|
+
if (!rule.methods ||
|
|
385
|
+
rule.methods.includes(reqMethod))
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return false;
|
|
391
|
+
};
|
|
392
|
+
if (gwConfig.middleware) {
|
|
393
|
+
const wrapped = gwConfig.middleware.map((m) => (req, res, next) => isExcluded(req.path, req.method) ? next() : m(req, res, next));
|
|
394
|
+
allMiddlewares.push(...wrapped);
|
|
395
|
+
}
|
|
396
|
+
if (gwConfig.filters) {
|
|
397
|
+
const wrapped = gwConfig.filters.map((f) => (err, req, res, next) => isExcluded(req.path, req.method)
|
|
398
|
+
? next(err)
|
|
399
|
+
: f.prototype && f.prototype.catch
|
|
400
|
+
? new f().catch(err, req, res, next)
|
|
401
|
+
: f(err, req, res, next));
|
|
402
|
+
allFilters.push(...wrapped);
|
|
403
|
+
}
|
|
404
|
+
if (gwConfig.guards) {
|
|
405
|
+
const wrapped = gwConfig.guards.map((g) => {
|
|
406
|
+
return async (req) => {
|
|
407
|
+
if (isExcluded(req.path, req.method))
|
|
408
|
+
return true;
|
|
409
|
+
return typeof g === 'function'
|
|
410
|
+
? g.prototype?.canActivate
|
|
411
|
+
? new g().canActivate(req)
|
|
412
|
+
: g(req)
|
|
413
|
+
: g.canActivate(req);
|
|
414
|
+
};
|
|
415
|
+
});
|
|
416
|
+
allGuards.push(...wrapped);
|
|
417
|
+
}
|
|
418
|
+
if (gwConfig.interceptors) {
|
|
419
|
+
const wrapped = gwConfig.interceptors.map((i) => {
|
|
420
|
+
return async (req, res) => {
|
|
421
|
+
if (isExcluded(req.path, req.method))
|
|
422
|
+
return;
|
|
423
|
+
return typeof i === 'function'
|
|
424
|
+
? i.prototype?.intercept
|
|
425
|
+
? new i().intercept(req, res)
|
|
426
|
+
: i(req, res)
|
|
427
|
+
: i.intercept(req, res);
|
|
428
|
+
};
|
|
429
|
+
});
|
|
430
|
+
allInterceptors.push(...wrapped);
|
|
431
|
+
}
|
|
432
|
+
if (gwConfig.timeout !== undefined) {
|
|
433
|
+
const baseTimeout = gwConfig.timeout;
|
|
434
|
+
const timeoutMiddleware = (req, res, next) => {
|
|
435
|
+
if (isExcluded(req.path, req.method))
|
|
436
|
+
return next();
|
|
437
|
+
const ms = typeof baseTimeout === 'function'
|
|
438
|
+
? baseTimeout(req)
|
|
439
|
+
: baseTimeout;
|
|
440
|
+
if (ms) {
|
|
441
|
+
req.timeoutTimer = setTimeout(() => {
|
|
442
|
+
if (!res.headersSent) {
|
|
443
|
+
res.status(408).json({
|
|
444
|
+
success: false,
|
|
445
|
+
error: {
|
|
446
|
+
code: 'REQUEST_TIMEOUT',
|
|
447
|
+
message: 'Request Timeout',
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}, ms);
|
|
452
|
+
res.raw.on('finish', () => clearTimeout(req.timeoutTimer));
|
|
453
|
+
}
|
|
454
|
+
next();
|
|
455
|
+
};
|
|
456
|
+
allMiddlewares.push(timeoutMiddleware);
|
|
457
|
+
}
|
|
458
|
+
if (gwConfig.metadata) {
|
|
459
|
+
allMetadata = { ...allMetadata, ...gwConfig.metadata };
|
|
460
|
+
}
|
|
461
|
+
if (gwConfig.cors !== undefined)
|
|
462
|
+
allCors = gwConfig.cors;
|
|
463
|
+
if (gwConfig.headers)
|
|
464
|
+
allHeaders = { ...allHeaders, ...gwConfig.headers };
|
|
465
|
+
if (gwConfig.plugins) {
|
|
466
|
+
for (const plugin of gwConfig.plugins) {
|
|
467
|
+
await this.app.pluginManager.register(plugin);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (gwConfig.imports) {
|
|
471
|
+
for (const mod of gwConfig.imports) {
|
|
472
|
+
const name = 'plugin' in mod ? mod.plugin.name : mod.name;
|
|
473
|
+
if (!this.app.pluginManager.hasPlugin(name)) {
|
|
474
|
+
await this.app.pluginManager.register(mod);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (gwConfig.providers) {
|
|
479
|
+
for (const [token, providerConfig] of gwConfig.providers) {
|
|
480
|
+
this.app.container.provide(token, providerConfig);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
/* ignore */
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const currentMod = mod && mod.default && mod.default.default
|
|
491
|
+
? mod.default.default
|
|
492
|
+
: mod && mod.default
|
|
493
|
+
? mod.default
|
|
494
|
+
: mod;
|
|
495
|
+
const isControllerObj = currentMod && currentMod.__isController === true;
|
|
496
|
+
const routeConfig = {
|
|
497
|
+
...(isControllerObj ? currentMod : {}),
|
|
498
|
+
...(mod.config || {}),
|
|
499
|
+
};
|
|
500
|
+
// Fallback to global app.options.cors if neither gateway nor route specify it
|
|
501
|
+
if (routeConfig.cors !== undefined)
|
|
502
|
+
allCors = routeConfig.cors;
|
|
503
|
+
else if (allCors === undefined && this.app.options.cors !== undefined)
|
|
504
|
+
allCors = this.app.options.cors;
|
|
505
|
+
if (routeConfig.middleware)
|
|
506
|
+
allMiddlewares.push(...(Array.isArray(routeConfig.middleware)
|
|
507
|
+
? routeConfig.middleware
|
|
508
|
+
: [routeConfig.middleware]));
|
|
509
|
+
if (routeConfig.headers)
|
|
510
|
+
allHeaders = { ...allHeaders, ...routeConfig.headers };
|
|
511
|
+
if (routeConfig.plugins) {
|
|
512
|
+
for (const plugin of routeConfig.plugins) {
|
|
513
|
+
await this.app.pluginManager.register(plugin);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
// Pass gateway features to controllers via metadata if necessary
|
|
517
|
+
const combinedFilters = [
|
|
518
|
+
...allFilters,
|
|
519
|
+
...(routeConfig.filters
|
|
520
|
+
? Array.isArray(routeConfig.filters)
|
|
521
|
+
? routeConfig.filters
|
|
522
|
+
: [routeConfig.filters]
|
|
523
|
+
: []),
|
|
524
|
+
];
|
|
525
|
+
const combinedGuards = [
|
|
526
|
+
...allGuards,
|
|
527
|
+
...(routeConfig.guards
|
|
528
|
+
? Array.isArray(routeConfig.guards)
|
|
529
|
+
? routeConfig.guards
|
|
530
|
+
: [routeConfig.guards]
|
|
531
|
+
: []),
|
|
532
|
+
];
|
|
533
|
+
const combinedInterceptors = [
|
|
534
|
+
...allInterceptors,
|
|
535
|
+
...(routeConfig.interceptors
|
|
536
|
+
? Array.isArray(routeConfig.interceptors)
|
|
537
|
+
? routeConfig.interceptors
|
|
538
|
+
: [routeConfig.interceptors]
|
|
539
|
+
: []),
|
|
540
|
+
];
|
|
541
|
+
// Inject into the default export if it's a functional controller
|
|
542
|
+
if (mod.default && typeof mod.default === 'object') {
|
|
543
|
+
try {
|
|
544
|
+
if (!mod.default.filters)
|
|
545
|
+
mod.default.filters = [];
|
|
546
|
+
mod.default.filters.push(...combinedFilters);
|
|
547
|
+
if (!mod.default.guards)
|
|
548
|
+
mod.default.guards = [];
|
|
549
|
+
mod.default.guards.push(...combinedGuards);
|
|
550
|
+
if (!mod.default.interceptors)
|
|
551
|
+
mod.default.interceptors = [];
|
|
552
|
+
mod.default.interceptors.push(...combinedInterceptors);
|
|
553
|
+
if (!mod.default.metadata)
|
|
554
|
+
mod.default.metadata = {};
|
|
555
|
+
Object.assign(mod.default.metadata, {
|
|
556
|
+
...allMetadata,
|
|
557
|
+
...(routeConfig.metadata || {}),
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
// If mod.default is frozen, ignore
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
const prefixMiddlewares = [];
|
|
565
|
+
if (this.app.options.debugRouting) {
|
|
566
|
+
const pathLib = await Promise.resolve().then(() => __importStar(require('node:path')));
|
|
567
|
+
const relFile = pathLib
|
|
568
|
+
.relative(process.cwd(), filePath)
|
|
569
|
+
.replace(/\\/g, '/');
|
|
570
|
+
const gwStr = activeGateways.length > 0
|
|
571
|
+
? activeGateways
|
|
572
|
+
.map((g) => pathLib.relative(process.cwd(), g).replace(/\\/g, '/'))
|
|
573
|
+
.join(' -> ') + ' -> '
|
|
574
|
+
: '';
|
|
575
|
+
prefixMiddlewares.push((req, res, next) => {
|
|
576
|
+
this.app.log.debug(`[Request] ${req.method} ${req.path} -> ${gwStr}${relFile}`);
|
|
577
|
+
next();
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
if (allCors !== undefined) {
|
|
581
|
+
if (allCors === true)
|
|
582
|
+
prefixMiddlewares.push((0, middleware_1.cors)({}));
|
|
583
|
+
else if (allCors !== false)
|
|
584
|
+
prefixMiddlewares.push((0, middleware_1.cors)(allCors));
|
|
585
|
+
}
|
|
586
|
+
if (Object.keys(allHeaders).length > 0) {
|
|
587
|
+
prefixMiddlewares.push((req, res, next) => {
|
|
588
|
+
for (const [k, v] of Object.entries(allHeaders))
|
|
589
|
+
res.setHeader(k, v);
|
|
590
|
+
next();
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
prefixMiddlewares.push(...allMiddlewares);
|
|
594
|
+
const isRouter = (obj) => obj && 'handle' in obj && 'get' in obj;
|
|
595
|
+
const CONTROLLER_PREFIX = Symbol.for('exisjs:controller_prefix');
|
|
596
|
+
const isController = (obj) => obj && obj.prototype && obj.prototype[CONTROLLER_PREFIX] !== undefined;
|
|
597
|
+
const unwrappedMod = mod && mod.default && mod.default.default
|
|
598
|
+
? mod.default.default
|
|
599
|
+
: mod && mod.default
|
|
600
|
+
? mod.default
|
|
601
|
+
: mod;
|
|
602
|
+
const functionalControllerObj = unwrappedMod && unwrappedMod.__isController ? unwrappedMod : null;
|
|
603
|
+
if (functionalControllerObj) {
|
|
604
|
+
// ─── THE NEW PERFECT FUNCTIONAL CONTROLLER ───
|
|
605
|
+
const compiledRouter = this.compileFunctionalController(functionalControllerObj);
|
|
606
|
+
this.mountRouteWithSource(routePath, compiledRouter, filePath, prefixMiddlewares);
|
|
607
|
+
}
|
|
608
|
+
else if (mod.router && isRouter(mod.router)) {
|
|
609
|
+
this.mountRouteWithSource(routePath, mod.router, filePath, prefixMiddlewares);
|
|
610
|
+
}
|
|
611
|
+
else if (unwrappedMod && isController(unwrappedMod)) {
|
|
612
|
+
this.app.registerControllers([unwrappedMod], routePath, prefixMiddlewares);
|
|
613
|
+
}
|
|
614
|
+
else if (unwrappedMod && isRouter(unwrappedMod)) {
|
|
615
|
+
this.mountRouteWithSource(routePath, unwrappedMod, filePath, prefixMiddlewares);
|
|
616
|
+
}
|
|
617
|
+
else if (isRouter(mod)) {
|
|
618
|
+
this.mountRouteWithSource(routePath, mod, filePath, prefixMiddlewares);
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
this.app.log.warn(`\n[WARN] Route file \x1b[36m${filePath}\x1b[0m does not export a recognized router or controller.\n`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
compileFunctionalController(config) {
|
|
625
|
+
const router = new router_1.Router();
|
|
626
|
+
const fileMiddleware = [];
|
|
627
|
+
if (config.cors) {
|
|
628
|
+
fileMiddleware.push(config.cors === true ? (0, middleware_1.cors)({}) : (0, middleware_1.cors)(config.cors));
|
|
629
|
+
}
|
|
630
|
+
if (config.middleware) {
|
|
631
|
+
fileMiddleware.push(...(Array.isArray(config.middleware)
|
|
632
|
+
? config.middleware
|
|
633
|
+
: [config.middleware]));
|
|
634
|
+
}
|
|
635
|
+
const { onError, onResponse } = config;
|
|
636
|
+
for (const [key, routeConfig] of Object.entries(config)) {
|
|
637
|
+
if ([
|
|
638
|
+
'cors',
|
|
639
|
+
'middleware',
|
|
640
|
+
'onError',
|
|
641
|
+
'onResponse',
|
|
642
|
+
'__isController',
|
|
643
|
+
].includes(key))
|
|
644
|
+
continue;
|
|
645
|
+
const rc = routeConfig;
|
|
646
|
+
if (!rc || !rc.method || !rc.path || !rc.handle)
|
|
647
|
+
continue;
|
|
648
|
+
const routeMiddlewares = [...fileMiddleware];
|
|
649
|
+
if (rc.cors) {
|
|
650
|
+
routeMiddlewares.push(rc.cors === true ? (0, middleware_1.cors)({}) : (0, middleware_1.cors)(rc.cors));
|
|
651
|
+
}
|
|
652
|
+
if (rc.middleware) {
|
|
653
|
+
routeMiddlewares.push(...(Array.isArray(rc.middleware) ? rc.middleware : [rc.middleware]));
|
|
654
|
+
}
|
|
655
|
+
// ─── SUPER HANDLER WRAPPER ───
|
|
656
|
+
const superHandler = async (req, res, next) => {
|
|
657
|
+
if (onResponse) {
|
|
658
|
+
res.raw.on('finish', () => onResponse(req, res));
|
|
659
|
+
}
|
|
660
|
+
try {
|
|
661
|
+
// 0. Enforce Route Permissions (Role Authorization)
|
|
662
|
+
if (rc.permissions && rc.permissions.length > 0) {
|
|
663
|
+
if (!req.user) {
|
|
664
|
+
res.status(401).json({
|
|
665
|
+
success: false,
|
|
666
|
+
error: {
|
|
667
|
+
code: 'UNAUTHORIZED',
|
|
668
|
+
message: 'Unauthorized: User not found on request',
|
|
669
|
+
},
|
|
670
|
+
});
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const userPerms = req.user.permissions || req.user.roles || req.user.role || [];
|
|
674
|
+
const permsArray = Array.isArray(userPerms)
|
|
675
|
+
? userPerms
|
|
676
|
+
: [userPerms];
|
|
677
|
+
const hasPerm = rc.permissions.every((p) => permsArray.includes(p));
|
|
678
|
+
if (!hasPerm) {
|
|
679
|
+
res.status(403).json({
|
|
680
|
+
success: false,
|
|
681
|
+
error: {
|
|
682
|
+
code: 'FORBIDDEN',
|
|
683
|
+
message: 'Forbidden: Insufficient permissions',
|
|
684
|
+
},
|
|
685
|
+
});
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
// 1. Run Guards
|
|
690
|
+
const routeGuards = [...(config.guards || []), ...(rc.guards || [])];
|
|
691
|
+
for (const guard of routeGuards) {
|
|
692
|
+
const allowed = await (typeof guard === 'function'
|
|
693
|
+
? guard.prototype?.canActivate
|
|
694
|
+
? new guard().canActivate(req)
|
|
695
|
+
: guard(req)
|
|
696
|
+
: guard.canActivate(req));
|
|
697
|
+
if (!allowed) {
|
|
698
|
+
if (res && !res.headersSent) {
|
|
699
|
+
res.status(403).json({
|
|
700
|
+
success: false,
|
|
701
|
+
error: { code: 'FORBIDDEN', message: 'Forbidden by Guard' },
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
// Build Context
|
|
708
|
+
const ctx = {
|
|
709
|
+
body: req.body,
|
|
710
|
+
query: req.query,
|
|
711
|
+
params: req.params,
|
|
712
|
+
headers: req.headers,
|
|
713
|
+
req,
|
|
714
|
+
res,
|
|
715
|
+
app: this,
|
|
716
|
+
socket: req.ws,
|
|
717
|
+
state: context_1.executionContext.getStore()?.state || {},
|
|
718
|
+
...req, // Spread req to allow access to user, session, etc.
|
|
719
|
+
};
|
|
720
|
+
const result = await rc.handle(ctx);
|
|
721
|
+
// 2. Run Interceptors
|
|
722
|
+
const routeInterceptors = [
|
|
723
|
+
...(config.interceptors || []),
|
|
724
|
+
...(rc.interceptors || []),
|
|
725
|
+
];
|
|
726
|
+
for (const interceptor of routeInterceptors) {
|
|
727
|
+
await (typeof interceptor === 'function'
|
|
728
|
+
? interceptor.prototype?.intercept
|
|
729
|
+
? new interceptor().intercept(req, res)
|
|
730
|
+
: interceptor(req, res)
|
|
731
|
+
: interceptor.intercept(req, res));
|
|
732
|
+
}
|
|
733
|
+
if (result !== undefined && !res.headersSent) {
|
|
734
|
+
if (typeof result === 'object' && result !== null) {
|
|
735
|
+
res.json(result);
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
res.send(String(result));
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
catch (err) {
|
|
743
|
+
if (onError) {
|
|
744
|
+
try {
|
|
745
|
+
await onError(err, req, res);
|
|
746
|
+
}
|
|
747
|
+
catch (hookErr) {
|
|
748
|
+
next(hookErr);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
next(err); // Hands off to ExisJS global error handler (which handles HttpErrors automatically!)
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
// Mount to router
|
|
757
|
+
const method = rc.method.toLowerCase();
|
|
758
|
+
const schema = {};
|
|
759
|
+
const isValidatorOrPipe = (v) => v.parse ||
|
|
760
|
+
v.transform ||
|
|
761
|
+
(typeof v === 'function' && v.prototype?.transform);
|
|
762
|
+
if (rc.body) {
|
|
763
|
+
schema.body = isValidatorOrPipe(rc.body) ? rc.body : validator_1.v.object(rc.body);
|
|
764
|
+
}
|
|
765
|
+
if (rc.query) {
|
|
766
|
+
schema.query = isValidatorOrPipe(rc.query)
|
|
767
|
+
? rc.query
|
|
768
|
+
: validator_1.v.object(rc.query);
|
|
769
|
+
}
|
|
770
|
+
if (rc.params) {
|
|
771
|
+
schema.params = isValidatorOrPipe(rc.params)
|
|
772
|
+
? rc.params
|
|
773
|
+
: validator_1.v.object(rc.params);
|
|
774
|
+
}
|
|
775
|
+
if (rc.host) {
|
|
776
|
+
schema.host = rc.host;
|
|
777
|
+
}
|
|
778
|
+
const combinedFilters = [
|
|
779
|
+
...(config.filters
|
|
780
|
+
? Array.isArray(config.filters)
|
|
781
|
+
? config.filters
|
|
782
|
+
: [config.filters]
|
|
783
|
+
: []),
|
|
784
|
+
...(rc.filters
|
|
785
|
+
? Array.isArray(rc.filters)
|
|
786
|
+
? rc.filters
|
|
787
|
+
: [rc.filters]
|
|
788
|
+
: []),
|
|
789
|
+
];
|
|
790
|
+
if (combinedFilters.length > 0) {
|
|
791
|
+
schema.filters = combinedFilters;
|
|
792
|
+
}
|
|
793
|
+
const combinedMetadata = {
|
|
794
|
+
...(config.metadata || {}),
|
|
795
|
+
...(rc.metadata || {}),
|
|
796
|
+
};
|
|
797
|
+
if (Object.keys(combinedMetadata).length > 0) {
|
|
798
|
+
schema.metadata = combinedMetadata;
|
|
799
|
+
}
|
|
800
|
+
if (method === 'sse') {
|
|
801
|
+
const sseHandler = async (stream, rawReq) => {
|
|
802
|
+
try {
|
|
803
|
+
const ctx = {
|
|
804
|
+
body: rawReq.body,
|
|
805
|
+
query: rawReq.query,
|
|
806
|
+
params: rawReq.params,
|
|
807
|
+
headers: rawReq.headers,
|
|
808
|
+
req: rawReq,
|
|
809
|
+
res: undefined, // Res is not available in standard SSE after handshake
|
|
810
|
+
app: this,
|
|
811
|
+
stream,
|
|
812
|
+
...rawReq,
|
|
813
|
+
};
|
|
814
|
+
await rc.handle(ctx);
|
|
815
|
+
}
|
|
816
|
+
catch (err) {
|
|
817
|
+
if (onError) {
|
|
818
|
+
await onError(err, rawReq, {});
|
|
819
|
+
}
|
|
820
|
+
else {
|
|
821
|
+
console.error('[Exis SSE Error]', err);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
router.sse(rc.path, ...routeMiddlewares, sseHandler);
|
|
826
|
+
}
|
|
827
|
+
else {
|
|
828
|
+
if (Object.keys(schema).length > 0) {
|
|
829
|
+
;
|
|
830
|
+
router[method](rc.path, ...routeMiddlewares, schema, superHandler);
|
|
831
|
+
}
|
|
832
|
+
else {
|
|
833
|
+
;
|
|
834
|
+
router[method](rc.path, ...routeMiddlewares, superHandler);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return router;
|
|
839
|
+
}
|
|
840
|
+
mountRouteWithSource(prefix, subRouter, sourceFile, prefixMiddlewares = []) {
|
|
841
|
+
for (const route of subRouter.getRoutes()) {
|
|
842
|
+
const fullPath = (prefix + route.path).replace(/\/+/g, '/');
|
|
843
|
+
const r = { ...route, path: fullPath, sourceFile };
|
|
844
|
+
if (prefixMiddlewares.length > 0) {
|
|
845
|
+
r.handlers = [...prefixMiddlewares, ...r.handlers];
|
|
846
|
+
}
|
|
847
|
+
this.app.getRouter().addRawRoute(r);
|
|
848
|
+
if (this.app.pluginManager.hooks.route.length > 0) {
|
|
849
|
+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
|
850
|
+
for (let i = 0; i < this.app.pluginManager.hooks.route.length; i++) {
|
|
851
|
+
this.app.pluginManager.hooks.route[i]({
|
|
852
|
+
method: route.method,
|
|
853
|
+
path: fullPath,
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
return this;
|
|
859
|
+
}
|
|
860
|
+
async scanDirectory(dir, baseRoute = '/') {
|
|
861
|
+
const fs = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
|
|
862
|
+
const path = await Promise.resolve().then(() => __importStar(require('node:path')));
|
|
863
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
864
|
+
const results = [];
|
|
865
|
+
for (const entry of entries) {
|
|
866
|
+
const fullPath = path.join(dir, entry.name);
|
|
867
|
+
if (entry.isDirectory()) {
|
|
868
|
+
let segment = entry.name;
|
|
869
|
+
if (segment.startsWith('(') && segment.endsWith(')')) {
|
|
870
|
+
const subResults = await this.scanDirectory(fullPath, baseRoute);
|
|
871
|
+
results.push(...subResults);
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
if (segment.startsWith('[...') && segment.endsWith(']')) {
|
|
875
|
+
segment = '*' + segment.slice(4, -1);
|
|
876
|
+
}
|
|
877
|
+
else {
|
|
878
|
+
segment = segment.replace(/\[(.*?)\]/g, ':$1');
|
|
879
|
+
}
|
|
880
|
+
const nextBase = baseRoute === '/' ? `/${segment}` : `${baseRoute}/${segment}`;
|
|
881
|
+
const subResults = await this.scanDirectory(fullPath, nextBase);
|
|
882
|
+
results.push(...subResults);
|
|
883
|
+
}
|
|
884
|
+
else {
|
|
885
|
+
results.push({ filePath: fullPath, routePath: baseRoute });
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
return results;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
exports.RouteScanner = RouteScanner;
|
|
892
|
+
//# sourceMappingURL=route-scanner.js.map
|