@yumerijs/core 1.1.2 → 1.2.0
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/command.d.ts +3 -0
- package/dist/command.js +3 -0
- package/dist/config.d.ts +0 -0
- package/dist/config.js +0 -0
- package/dist/context.d.ts +42 -0
- package/dist/context.js +56 -0
- package/dist/core.d.ts +63 -16
- package/dist/core.js +168 -58
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/logger.d.ts +1 -0
- package/dist/logger.js +16 -116
- package/dist/platform.d.ts +0 -0
- package/dist/platform.js +0 -0
- package/dist/route.d.ts +58 -0
- package/dist/route.js +221 -0
- package/dist/session.d.ts +0 -0
- package/dist/session.js +0 -0
- package/package.json +4 -6
- package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/core.js
CHANGED
|
@@ -32,9 +32,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
36
|
exports.Core = void 0;
|
|
40
37
|
/**
|
|
@@ -48,7 +45,8 @@ const path = __importStar(require("path"));
|
|
|
48
45
|
const config_1 = require("./config");
|
|
49
46
|
const logger_1 = require("./logger");
|
|
50
47
|
const command_1 = require("./command");
|
|
51
|
-
const
|
|
48
|
+
const chokidar = __importStar(require("chokidar"));
|
|
49
|
+
const route_1 = require("./route");
|
|
52
50
|
const context_1 = require("./context");
|
|
53
51
|
class Core {
|
|
54
52
|
plugins = {};
|
|
@@ -57,18 +55,46 @@ class Core {
|
|
|
57
55
|
eventListeners = {};
|
|
58
56
|
components = {};
|
|
59
57
|
commands = {};
|
|
58
|
+
routes = {};
|
|
60
59
|
pluginLoader;
|
|
61
60
|
logger = new logger_1.Logger('core');
|
|
62
61
|
providedComponents = {};
|
|
63
62
|
pluginModules = {};
|
|
64
63
|
configPath = '';
|
|
65
64
|
globalMiddlewares = {};
|
|
65
|
+
/**
|
|
66
|
+
* @deprecated 命令与插件对应关系
|
|
67
|
+
*/
|
|
66
68
|
cmdtoplu = {};
|
|
69
|
+
/**
|
|
70
|
+
* 路由与插件对应关系
|
|
71
|
+
*/
|
|
72
|
+
routetoplu = {};
|
|
73
|
+
/**
|
|
74
|
+
* 组件与插件对应关系
|
|
75
|
+
*/
|
|
67
76
|
comtoplu = {};
|
|
77
|
+
/**
|
|
78
|
+
* 事件监听器与插件对应关系
|
|
79
|
+
*/
|
|
68
80
|
evttoplu = {};
|
|
81
|
+
/**
|
|
82
|
+
* 中间件与插件对应关系
|
|
83
|
+
*/
|
|
69
84
|
mdwtoplu = {};
|
|
70
85
|
plftoplu = {};
|
|
86
|
+
/**
|
|
87
|
+
* 插件状态
|
|
88
|
+
*/
|
|
71
89
|
pluginStatus = {};
|
|
90
|
+
/**
|
|
91
|
+
* 插件监听器
|
|
92
|
+
*/
|
|
93
|
+
pluginWatchers = {};
|
|
94
|
+
/**
|
|
95
|
+
* 初始化核心
|
|
96
|
+
* @param pluginLoader 插件加载器
|
|
97
|
+
*/
|
|
72
98
|
constructor(pluginLoader) {
|
|
73
99
|
this.pluginLoader = pluginLoader;
|
|
74
100
|
}
|
|
@@ -96,7 +122,7 @@ class Core {
|
|
|
96
122
|
* @param configPath 配置文件的路径
|
|
97
123
|
*/
|
|
98
124
|
watchConfig(configPath) {
|
|
99
|
-
const watcher =
|
|
125
|
+
const watcher = chokidar.watch(configPath, {
|
|
100
126
|
persistent: true,
|
|
101
127
|
ignoreInitial: true,
|
|
102
128
|
awaitWriteFinish: {
|
|
@@ -143,6 +169,7 @@ class Core {
|
|
|
143
169
|
}
|
|
144
170
|
/**
|
|
145
171
|
* 加载所有在配置文件中启用的插件
|
|
172
|
+
* @returns Promise<void>
|
|
146
173
|
*/
|
|
147
174
|
async loadPlugins() {
|
|
148
175
|
if (!this.config || typeof this.config.plugins !== 'object' || this.config.plugins === null) {
|
|
@@ -162,6 +189,13 @@ class Core {
|
|
|
162
189
|
}
|
|
163
190
|
}
|
|
164
191
|
const enabledPlugins = allPluginNames.filter(name => !name.startsWith('~'));
|
|
192
|
+
// 卸载配置中已禁用或移除的已加载插件
|
|
193
|
+
const currentlyLoaded = Object.keys(this.plugins);
|
|
194
|
+
for (const loadedName of currentlyLoaded) {
|
|
195
|
+
if (!enabledPlugins.includes(loadedName)) {
|
|
196
|
+
await this.unloadPlugin(loadedName);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
165
199
|
if (enabledPlugins.length === 0) {
|
|
166
200
|
this.logger.info('No enabled plugins found in configuration.');
|
|
167
201
|
return;
|
|
@@ -189,23 +223,20 @@ class Core {
|
|
|
189
223
|
if (pendingPlugins.length > 0) {
|
|
190
224
|
// this.logger.warn('Some plugins could not be loaded due to unresolved dependencies:', pendingPlugins);
|
|
191
225
|
}
|
|
192
|
-
if (process.env.NODE_ENV === 'development') {
|
|
193
|
-
this.watchPlugins(this);
|
|
194
|
-
}
|
|
195
226
|
}
|
|
196
227
|
/**
|
|
197
|
-
*
|
|
228
|
+
* 加载单个插件
|
|
198
229
|
* @param pluginName 要加载的插件名
|
|
199
230
|
* @param triggerPendingCheck 是否在加载后触发对其他待定插件的检查,默认为 true
|
|
200
231
|
* @returns Promise<boolean> 是否加载成功
|
|
201
232
|
*/
|
|
202
|
-
async loadSinglePlugin(pluginName, triggerPendingCheck = true) {
|
|
233
|
+
async loadSinglePlugin(pluginName, triggerPendingCheck = true, onlypending = false) {
|
|
203
234
|
// 如果插件不存在于状态记录中(例如,动态添加),则设为 PENDING
|
|
204
235
|
if (!this.pluginStatus[pluginName]) {
|
|
205
236
|
this.pluginStatus[pluginName] = "pending" /* PluginStatus.PENDING */;
|
|
206
237
|
}
|
|
207
238
|
// 只有 PENDING 状态的插件才能被加载
|
|
208
|
-
if (this.pluginStatus[pluginName] !== "pending" /* PluginStatus.PENDING */) {
|
|
239
|
+
if (this.pluginStatus[pluginName] !== "pending" /* PluginStatus.PENDING */ && onlypending) {
|
|
209
240
|
// this.logger.info(`Plugin "${pluginName}" is not in a pending state (current: ${this.pluginStatus[pluginName]}). Skipping load.`);
|
|
210
241
|
return false;
|
|
211
242
|
}
|
|
@@ -223,8 +254,8 @@ class Core {
|
|
|
223
254
|
// 依赖满足,开始加载
|
|
224
255
|
this.plugins[pluginName] = pluginInstance;
|
|
225
256
|
if (pluginInstance.apply) {
|
|
257
|
+
this.pluginLoader.logger.info(`Apply plugin "${pluginName}"`);
|
|
226
258
|
await pluginInstance.apply(new context_1.Context(this, pluginName), await this.getPluginConfig(pluginName));
|
|
227
|
-
this.pluginLoader.logger.info(`Applied plugin "${pluginName}"`);
|
|
228
259
|
}
|
|
229
260
|
// 注册提供的组件
|
|
230
261
|
if (pluginInstance.provide) {
|
|
@@ -238,11 +269,34 @@ class Core {
|
|
|
238
269
|
}
|
|
239
270
|
}
|
|
240
271
|
this.pluginStatus[pluginName] = "enabled" /* PluginStatus.ENABLED */; // 更新状态为已启用
|
|
241
|
-
// this.logger.info(`Plugin "${pluginName}" loaded successfully.`);
|
|
242
272
|
// 加载成功后,检查是否可以加载其他等待中的插件
|
|
243
273
|
if (triggerPendingCheck) {
|
|
244
274
|
await this._loadPendingPlugins();
|
|
245
275
|
}
|
|
276
|
+
// In development mode, watch the plugin for changes.
|
|
277
|
+
if (process.env.NODE_ENV === 'development') {
|
|
278
|
+
let pluginPathToWatch = null;
|
|
279
|
+
try {
|
|
280
|
+
const pkgJsonPath = require.resolve(`${pluginName}/package.json`);
|
|
281
|
+
pluginPathToWatch = path.dirname(pkgJsonPath);
|
|
282
|
+
}
|
|
283
|
+
catch (e) {
|
|
284
|
+
const localPluginPath = path.resolve(process.cwd(), pluginName);
|
|
285
|
+
const localPluginPathInPlugins = path.resolve(process.cwd(), 'plugins', pluginName);
|
|
286
|
+
if (fs.existsSync(localPluginPath)) {
|
|
287
|
+
pluginPathToWatch = localPluginPath;
|
|
288
|
+
}
|
|
289
|
+
else if (fs.existsSync(localPluginPathInPlugins)) {
|
|
290
|
+
pluginPathToWatch = localPluginPathInPlugins;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (pluginPathToWatch) {
|
|
294
|
+
this.watchPlugin(pluginName, pluginPathToWatch);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
this.logger.warn(`Could not resolve path for plugin ${pluginName} to watch for changes.`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
246
300
|
return true;
|
|
247
301
|
}
|
|
248
302
|
catch (err) {
|
|
@@ -252,7 +306,8 @@ class Core {
|
|
|
252
306
|
}
|
|
253
307
|
}
|
|
254
308
|
/**
|
|
255
|
-
*
|
|
309
|
+
* 尝试加载所有处于 PENDING 状态的插件
|
|
310
|
+
* @returns Promise<void>
|
|
256
311
|
*/
|
|
257
312
|
async _loadPendingPlugins() {
|
|
258
313
|
const pendingPlugins = Object.keys(this.pluginStatus).filter(p => this.pluginStatus[p] === "pending" /* PluginStatus.PENDING */);
|
|
@@ -267,7 +322,7 @@ class Core {
|
|
|
267
322
|
* 卸载插件,并递归卸载依赖于它的其他插件
|
|
268
323
|
* @param pluginNameToUnload 要卸载的插件名
|
|
269
324
|
*/
|
|
270
|
-
async unloadPlugin(pluginNameToUnload) {
|
|
325
|
+
async unloadPlugin(pluginNameToUnload, ispending = false) {
|
|
271
326
|
// 1. 找出所有直接或间接依赖于此插件的插件
|
|
272
327
|
const dependents = [];
|
|
273
328
|
const pluginsToCheck = [pluginNameToUnload];
|
|
@@ -291,18 +346,19 @@ class Core {
|
|
|
291
346
|
}
|
|
292
347
|
// 2. 卸载所有依赖者
|
|
293
348
|
for (const dependentName of dependents) {
|
|
294
|
-
await this.
|
|
349
|
+
await this.unloadPlugin(dependentName, true);
|
|
295
350
|
}
|
|
296
351
|
// 3. 最后卸载目标插件
|
|
297
|
-
await this._unloadSinglePlugin(pluginNameToUnload);
|
|
352
|
+
await this._unloadSinglePlugin(pluginNameToUnload, ispending);
|
|
298
353
|
// 4. 卸载完成后,尝试重新加载处于 PENDING 状态的插件
|
|
299
354
|
// await this._loadPendingPlugins();
|
|
300
355
|
}
|
|
301
356
|
/**
|
|
302
|
-
*
|
|
357
|
+
* 执行单个插件的卸载逻辑
|
|
303
358
|
* @param pluginName 插件名
|
|
359
|
+
* @param ispending 是否处于 PENDING 状态
|
|
304
360
|
*/
|
|
305
|
-
async _unloadSinglePlugin(pluginName) {
|
|
361
|
+
async _unloadSinglePlugin(pluginName, ispending = false) {
|
|
306
362
|
if (this.pluginStatus[pluginName] !== "enabled" /* PluginStatus.ENABLED */) {
|
|
307
363
|
return; // 只卸载已启用的插件
|
|
308
364
|
}
|
|
@@ -316,11 +372,13 @@ class Core {
|
|
|
316
372
|
this.unregall(pluginName); // 清理该插件注册的所有内容
|
|
317
373
|
delete this.plugins[pluginName];
|
|
318
374
|
delete this.pluginModules[pluginName];
|
|
319
|
-
|
|
320
|
-
//
|
|
321
|
-
this.
|
|
375
|
+
this.pluginStatus[pluginName] = ispending ? "pending" /* PluginStatus.PENDING */ : "disabled" /* PluginStatus.DISABLED */;
|
|
376
|
+
// Stop watching the plugin directory
|
|
377
|
+
if (this.pluginWatchers[pluginName]) {
|
|
378
|
+
this.pluginWatchers[pluginName].close();
|
|
379
|
+
delete this.pluginWatchers[pluginName];
|
|
380
|
+
}
|
|
322
381
|
this.emit('plugin-unloaded', pluginName);
|
|
323
|
-
// this.logger.info(`Plugin "${pluginName}" unloaded.`);
|
|
324
382
|
}
|
|
325
383
|
catch (error) {
|
|
326
384
|
this.logger.error(`Failed to unload plugin "${pluginName}":`, error);
|
|
@@ -346,14 +404,17 @@ class Core {
|
|
|
346
404
|
}
|
|
347
405
|
}
|
|
348
406
|
/**
|
|
349
|
-
*
|
|
350
|
-
* @param
|
|
351
|
-
* @param
|
|
407
|
+
* 监听插件文件变化
|
|
408
|
+
* @param pluginName 插件名称
|
|
409
|
+
* @param pluginPath 插件路径
|
|
352
410
|
*/
|
|
353
|
-
|
|
411
|
+
watchPlugin(pluginName, pluginPath) {
|
|
412
|
+
if (this.pluginWatchers[pluginName]) {
|
|
413
|
+
return; // Already watching
|
|
414
|
+
}
|
|
354
415
|
const logger = new logger_1.Logger('hmr');
|
|
355
|
-
const watcher =
|
|
356
|
-
ignored: /(^|[
|
|
416
|
+
const watcher = chokidar.watch(pluginPath, {
|
|
417
|
+
ignored: /(^|[\/])\../,
|
|
357
418
|
persistent: true,
|
|
358
419
|
ignoreInitial: true,
|
|
359
420
|
awaitWriteFinish: {
|
|
@@ -361,34 +422,20 @@ class Core {
|
|
|
361
422
|
pollInterval: 100,
|
|
362
423
|
},
|
|
363
424
|
});
|
|
364
|
-
const getPluginNameFromPath = (changePath) => {
|
|
365
|
-
if (!changePath.endsWith('.ts') && !changePath.endsWith('.js'))
|
|
366
|
-
return null;
|
|
367
|
-
const parts = changePath.split(path.sep);
|
|
368
|
-
return parts.length > 1 ? parts[1] : null;
|
|
369
|
-
};
|
|
370
425
|
watcher.on('change', async (changePath) => {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
logger.info(`Plugin file changed: ${changePath}`);
|
|
374
|
-
await core.reloadPlugin(pluginName);
|
|
375
|
-
}
|
|
426
|
+
logger.info(`Plugin file changed: ${changePath}`);
|
|
427
|
+
await this.reloadPlugin(pluginName);
|
|
376
428
|
});
|
|
377
429
|
watcher.on('add', async (changePath) => {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
logger.info(`New plugin file added: ${changePath}`);
|
|
381
|
-
await core.reloadPlugin(pluginName);
|
|
382
|
-
}
|
|
430
|
+
logger.info(`New file added to plugin ${pluginName}: ${changePath}`);
|
|
431
|
+
await this.reloadPlugin(pluginName);
|
|
383
432
|
});
|
|
384
433
|
watcher.on('unlink', async (changePath) => {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
logger.info(`Plugin file removed: ${changePath}`);
|
|
388
|
-
await core.unloadPlugin(pluginName);
|
|
389
|
-
}
|
|
434
|
+
logger.info(`File removed from plugin ${pluginName}: ${changePath}`);
|
|
435
|
+
await this.reloadPlugin(pluginName);
|
|
390
436
|
});
|
|
391
|
-
|
|
437
|
+
this.pluginWatchers[pluginName] = watcher;
|
|
438
|
+
// logger.info(`Watching for changes in plugin: ${pluginName}`);
|
|
392
439
|
}
|
|
393
440
|
/**
|
|
394
441
|
* 注册组件
|
|
@@ -459,21 +506,26 @@ class Core {
|
|
|
459
506
|
return this;
|
|
460
507
|
}
|
|
461
508
|
/**
|
|
462
|
-
*
|
|
463
|
-
* @param name 命令名称
|
|
464
|
-
* @returns Command 实例
|
|
509
|
+
* @deprecated 已更改为路由
|
|
465
510
|
*/
|
|
466
511
|
command(name) {
|
|
467
512
|
const command = new command_1.Command(this, name);
|
|
468
513
|
this.commands[name] = command;
|
|
469
514
|
return command;
|
|
470
515
|
}
|
|
516
|
+
/**
|
|
517
|
+
* 注册路由
|
|
518
|
+
* @param path 路由路径
|
|
519
|
+
* @returns Route 实例
|
|
520
|
+
*/
|
|
521
|
+
route(path) {
|
|
522
|
+
const route = new route_1.Route(path);
|
|
523
|
+
this.routes[path] = route;
|
|
524
|
+
return route;
|
|
525
|
+
}
|
|
471
526
|
/**
|
|
472
527
|
* 执行命令
|
|
473
|
-
* @
|
|
474
|
-
* @param session 会话对象
|
|
475
|
-
* @param args 传递给命令处理函数的参数
|
|
476
|
-
* @returns 会话对象或 null
|
|
528
|
+
* @deprecated
|
|
477
529
|
*/
|
|
478
530
|
async executeCommand(name, session, ...args) {
|
|
479
531
|
const command = this.commands[name];
|
|
@@ -496,6 +548,37 @@ class Core {
|
|
|
496
548
|
}
|
|
497
549
|
return null;
|
|
498
550
|
}
|
|
551
|
+
/**
|
|
552
|
+
* 执行路由
|
|
553
|
+
* @param pathname 需要匹配的URL
|
|
554
|
+
* @param session 当前会话
|
|
555
|
+
* @param queryParams URL参数
|
|
556
|
+
* @returns 是否匹配成功
|
|
557
|
+
*/
|
|
558
|
+
async executeRoute(pathname, session, queryParams) {
|
|
559
|
+
for (const routePath in this.routes) {
|
|
560
|
+
const route = this.routes[routePath];
|
|
561
|
+
const result = route.match(pathname);
|
|
562
|
+
if (result) {
|
|
563
|
+
const globalMiddlewareList = Object.values(this.globalMiddlewares || {});
|
|
564
|
+
const routeMiddlewareList = route.middlewares || [];
|
|
565
|
+
const middlewares = [...globalMiddlewareList, ...routeMiddlewareList];
|
|
566
|
+
let index = 0;
|
|
567
|
+
const runner = async () => {
|
|
568
|
+
if (index < middlewares.length) {
|
|
569
|
+
const middleware = middlewares[index++];
|
|
570
|
+
await middleware(session, runner);
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
await route.executeHandler(session, queryParams, result.pathParams);
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
await runner();
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
499
582
|
/**
|
|
500
583
|
* 注册平台
|
|
501
584
|
* @param platform 平台实例
|
|
@@ -517,6 +600,13 @@ class Core {
|
|
|
517
600
|
delete this.cmdtoplu[cmd];
|
|
518
601
|
delete this.commands[cmd];
|
|
519
602
|
});
|
|
603
|
+
// 清理 routes
|
|
604
|
+
Object.keys(this.routetoplu)
|
|
605
|
+
.filter(path => this.routetoplu[path] === pluginname)
|
|
606
|
+
.forEach(path => {
|
|
607
|
+
delete this.routetoplu[path];
|
|
608
|
+
delete this.routes[path];
|
|
609
|
+
});
|
|
520
610
|
// 清理 components 和 providedComponents
|
|
521
611
|
Object.keys(this.comtoplu)
|
|
522
612
|
.filter(comp => this.comtoplu[comp] === pluginname)
|
|
@@ -525,6 +615,12 @@ class Core {
|
|
|
525
615
|
delete this.components[comp];
|
|
526
616
|
delete this.providedComponents[comp];
|
|
527
617
|
});
|
|
618
|
+
// 清理由 provide 声明的组件提供关系
|
|
619
|
+
Object.keys(this.providedComponents)
|
|
620
|
+
.filter(prov => this.providedComponents[prov] === pluginname)
|
|
621
|
+
.forEach(prov => {
|
|
622
|
+
delete this.providedComponents[prov];
|
|
623
|
+
});
|
|
528
624
|
// 清理 event listeners
|
|
529
625
|
for (const evt in this.evttoplu) {
|
|
530
626
|
if (this.evttoplu[evt][pluginname]) {
|
|
@@ -551,5 +647,19 @@ class Core {
|
|
|
551
647
|
getCommand(name) {
|
|
552
648
|
return this.commands[name] || null;
|
|
553
649
|
}
|
|
650
|
+
/**
|
|
651
|
+
* 获取路由
|
|
652
|
+
* @param path 匹配路径
|
|
653
|
+
* @return Route 实例或 false
|
|
654
|
+
*/
|
|
655
|
+
getRoute(path) {
|
|
656
|
+
for (const routePath in this.routes) {
|
|
657
|
+
const route = this.routes[routePath];
|
|
658
|
+
if (route.match(path)) {
|
|
659
|
+
return route;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
554
664
|
}
|
|
555
665
|
exports.Core = Core;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
22
22
|
__exportStar(require("./core"), exports);
|
|
23
23
|
__exportStar(require("./session"), exports);
|
|
24
24
|
__exportStar(require("./command"), exports);
|
|
25
|
+
__exportStar(require("./route"), exports);
|
|
25
26
|
__exportStar(require("./config"), exports);
|
|
26
27
|
__exportStar(require("./logger"), exports);
|
|
27
28
|
__exportStar(require("./platform"), exports);
|
package/dist/logger.d.ts
CHANGED
package/dist/logger.js
CHANGED
|
@@ -9,7 +9,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
9
9
|
};
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.Logger = void 0;
|
|
12
|
-
const
|
|
12
|
+
const picocolors_1 = __importDefault(require("picocolors")); // 轻量颜色库
|
|
13
13
|
class Logger {
|
|
14
14
|
title;
|
|
15
15
|
titleColor;
|
|
@@ -27,126 +27,26 @@ class Logger {
|
|
|
27
27
|
this.titleColor = this.getRandomColor();
|
|
28
28
|
}
|
|
29
29
|
getRandomColor() {
|
|
30
|
-
const availableColors = [
|
|
30
|
+
const availableColors = [
|
|
31
|
+
picocolors_1.default.red, picocolors_1.default.green, picocolors_1.default.yellow, picocolors_1.default.blue,
|
|
32
|
+
picocolors_1.default.magenta, picocolors_1.default.cyan, picocolors_1.default.white, picocolors_1.default.gray
|
|
33
|
+
];
|
|
31
34
|
const randomIndex = Math.floor(Math.random() * availableColors.length);
|
|
32
35
|
return availableColors[randomIndex];
|
|
33
36
|
}
|
|
34
|
-
|
|
37
|
+
getTimestamp() {
|
|
35
38
|
const now = new Date();
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
Logger.coreInstance?.emit('log', { level, message: args.join(' ') });
|
|
39
|
-
}
|
|
40
|
-
info(...args) {
|
|
41
|
-
this.log('I', ...args);
|
|
42
|
-
}
|
|
43
|
-
warn(...args) {
|
|
44
|
-
this.log('W', ...args);
|
|
39
|
+
return `${now.getFullYear()}/${String(now.getMonth() + 1).padStart(2, '0')}/${String(now.getDate()).padStart(2, '0')} ` +
|
|
40
|
+
`${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
|
|
45
41
|
}
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
log(level, ...args) {
|
|
43
|
+
const levelColor = level === 'E' ? picocolors_1.default.red : level === 'W' ? picocolors_1.default.yellow : picocolors_1.default.cyan;
|
|
44
|
+
const timestamp = this.getTimestamp();
|
|
45
|
+
console.log(`${picocolors_1.default.gray(timestamp)} [${levelColor(level)}] ${this.titleColor(this.title)}`, ...args);
|
|
46
|
+
Logger.coreInstance?.emit('log', { level, message: args.join(' ') });
|
|
48
47
|
}
|
|
48
|
+
info(...args) { this.log('I', ...args); }
|
|
49
|
+
warn(...args) { this.log('W', ...args); }
|
|
50
|
+
error(...args) { this.log('E', ...args); }
|
|
49
51
|
}
|
|
50
52
|
exports.Logger = Logger;
|
|
51
|
-
function stringifyDebug(value, seen = new WeakSet(), depth = 0, indent = 2, maxDepth = 5) {
|
|
52
|
-
const pad = ' '.repeat(depth * indent);
|
|
53
|
-
const nextPad = ' '.repeat((depth + 1) * indent);
|
|
54
|
-
// --- 1. 处理基本类型和 null ---
|
|
55
|
-
if (value === null) {
|
|
56
|
-
return 'null';
|
|
57
|
-
}
|
|
58
|
-
const type = typeof value;
|
|
59
|
-
if (type === 'string') {
|
|
60
|
-
// 确保 value 是字符串,并正确转义内部单引号
|
|
61
|
-
return `'${value.replace(/'/g, "\\'")}'`;
|
|
62
|
-
}
|
|
63
|
-
if (type === 'number' || type === 'boolean') {
|
|
64
|
-
return String(value);
|
|
65
|
-
}
|
|
66
|
-
if (type === 'undefined') {
|
|
67
|
-
return 'undefined';
|
|
68
|
-
}
|
|
69
|
-
if (type === 'symbol') {
|
|
70
|
-
return value.toString();
|
|
71
|
-
}
|
|
72
|
-
if (type === 'function') {
|
|
73
|
-
// 类型断言,确保可以访问函数的 name 属性
|
|
74
|
-
return `[Function: ${value.name || '(anonymous)'}]`;
|
|
75
|
-
}
|
|
76
|
-
if (type === 'bigint') {
|
|
77
|
-
// BigInt 的特殊表示,确保 value 是 BigInt 类型
|
|
78
|
-
return `${String(value)}n`;
|
|
79
|
-
}
|
|
80
|
-
// At this point, 'value' must be a non-primitive object type (object, array, Map, Set, Date, RegExp, custom class)
|
|
81
|
-
// --- 2. 处理简单的内置对象 (它们不会有循环引用问题) ---
|
|
82
|
-
if (value instanceof RegExp) {
|
|
83
|
-
return value.toString();
|
|
84
|
-
}
|
|
85
|
-
if (value instanceof Date) {
|
|
86
|
-
return value.toISOString();
|
|
87
|
-
}
|
|
88
|
-
// --- 3. 深度限制检查 ---
|
|
89
|
-
// 此时 value 确定是一个可以被 seen WeakSet 存储的对象
|
|
90
|
-
if (depth >= maxDepth) {
|
|
91
|
-
return '[Object]';
|
|
92
|
-
}
|
|
93
|
-
// --- 4. 循环引用检测 ---
|
|
94
|
-
// 将当前对象添加到 seen Set,以便在后续递归中检测循环引用
|
|
95
|
-
// 这里需要断言 value 是一个 object 类型,因为 WeakSet 只能存储对象
|
|
96
|
-
if (seen.has(value)) {
|
|
97
|
-
return '[Circular]';
|
|
98
|
-
}
|
|
99
|
-
seen.add(value); // 添加到已访问集合
|
|
100
|
-
// --- 5. 处理复杂内置对象 (Array, Map, Set) ---
|
|
101
|
-
if (Array.isArray(value)) {
|
|
102
|
-
// 在这一分支中,TypeScript 已经把 value 缩小为 unknown[] 类型
|
|
103
|
-
if (value.length === 0)
|
|
104
|
-
return '[]';
|
|
105
|
-
const elements = value.map(el => stringifyDebug(el, seen, depth + 1, indent, maxDepth));
|
|
106
|
-
return `[\n${nextPad}${elements.join(`,\n${nextPad}`)}\n${pad}]`;
|
|
107
|
-
}
|
|
108
|
-
if (value instanceof Map) {
|
|
109
|
-
// 在这一分支中,TypeScript 已经把 value 缩小为 Map<unknown, unknown> 类型
|
|
110
|
-
if (value.size === 0)
|
|
111
|
-
return 'Map(0) {}';
|
|
112
|
-
const entries = Array.from(value.entries()).map(([k, v]) => `${stringifyDebug(k, seen, depth + 1, indent, maxDepth)} => ${stringifyDebug(v, seen, depth + 1, indent, maxDepth)}`);
|
|
113
|
-
return `Map(${value.size}) {\n${nextPad}${entries.join(`,\n${nextPad}`)}\n${pad}}`;
|
|
114
|
-
}
|
|
115
|
-
if (value instanceof Set) {
|
|
116
|
-
// 在这一分支中,TypeScript 已经把 value 缩小为 Set<unknown> 类型
|
|
117
|
-
if (value.size === 0)
|
|
118
|
-
return 'Set(0) {}';
|
|
119
|
-
const elements = Array.from(value.values()).map(el => stringifyDebug(el, seen, depth + 1, indent, maxDepth));
|
|
120
|
-
return `Set(${value.size}) {\n${nextPad}${elements.join(`,\n${nextPad}`)}\n${pad}}`;
|
|
121
|
-
}
|
|
122
|
-
// --- 6. 处理普通对象和自定义类的实例 ---
|
|
123
|
-
// 此时,value 确定是一个对象 (除了上面已处理的 Array, Map, Set, Date, RegExp, primitive, null)
|
|
124
|
-
// 并且它已经添加到了 seen 集合
|
|
125
|
-
const keys = Object.keys(value); // 将 value 断言为 object,确保 Object.keys 可以接受
|
|
126
|
-
// 获取构造函数名(如果不是普通的 Object)
|
|
127
|
-
let constructorName = '';
|
|
128
|
-
// 同样,确保 value 被视为 object 以访问 constructor 属性
|
|
129
|
-
if (value.constructor && value.constructor.name && value.constructor.name !== 'Object') {
|
|
130
|
-
constructorName = `${value.constructor.name} `;
|
|
131
|
-
}
|
|
132
|
-
if (keys.length === 0) {
|
|
133
|
-
return `${constructorName}{}`; // 空对象/空类实例
|
|
134
|
-
}
|
|
135
|
-
const properties = keys.map(key => {
|
|
136
|
-
// 将 value 断言为带有索引签名的 Record,以便可以通过字符串键访问属性
|
|
137
|
-
const propValue = value[key];
|
|
138
|
-
return `${nextPad}${normalizeObjectKey(key)}: ${stringifyDebug(propValue, seen, depth + 1, indent, maxDepth)}`;
|
|
139
|
-
});
|
|
140
|
-
return `${constructorName}{\n${properties.join(',\n')}\n${pad}}`;
|
|
141
|
-
}
|
|
142
|
-
// 辅助函数:格式化对象键名,确保它在字面量中是有效的
|
|
143
|
-
function normalizeObjectKey(key) {
|
|
144
|
-
// 检查 key 是否是有效的 JavaScript 标识符,且不是保留字
|
|
145
|
-
// 简化的判断:如果包含非字母数字下划线,或以数字开头,则需要引用。
|
|
146
|
-
// 更严谨的判断需要解析器级别的检查。这里我们简单处理为如果包含特殊字符或空格就引用。
|
|
147
|
-
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) && !['null', 'undefined', 'true', 'false'].includes(key)) {
|
|
148
|
-
return key; // 不需要引用
|
|
149
|
-
}
|
|
150
|
-
// 引用并转义内部的单引号
|
|
151
|
-
return `'${key.replace(/'/g, "\\'")}'`;
|
|
152
|
-
}
|
package/dist/platform.d.ts
CHANGED
|
File without changes
|
package/dist/platform.js
CHANGED
|
File without changes
|
package/dist/route.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Session } from './session';
|
|
2
|
+
import { Middleware } from './middleware';
|
|
3
|
+
export type RouteHandler = (session: Session, queryParams: URLSearchParams, ...pathParams: string[]) => Promise<void> | void;
|
|
4
|
+
/**
|
|
5
|
+
* Route class using segment-based matching (no fragile capture-group-index mapping).
|
|
6
|
+
* Behavior matches the SimpleRouter in the webpage:
|
|
7
|
+
* - pathname and pattern are split on '/'
|
|
8
|
+
* - supports :param (single required), :param? (single optional), :param+ (one or more segments),
|
|
9
|
+
* :param* (zero or more segments)
|
|
10
|
+
* - honors "no trailing slash" expectation (we normalize input)
|
|
11
|
+
*/
|
|
12
|
+
export declare class Route {
|
|
13
|
+
path: string;
|
|
14
|
+
private segments;
|
|
15
|
+
private paramsInfo;
|
|
16
|
+
private handler;
|
|
17
|
+
middlewares: Middleware[];
|
|
18
|
+
allowedMethods: string[];
|
|
19
|
+
/**
|
|
20
|
+
* 创建路由
|
|
21
|
+
* @param path 路由路径
|
|
22
|
+
*/
|
|
23
|
+
constructor(path: string);
|
|
24
|
+
/**
|
|
25
|
+
* 设置路由处理器
|
|
26
|
+
* @param handler 路由处理器
|
|
27
|
+
* @returns this
|
|
28
|
+
*/
|
|
29
|
+
action(handler: RouteHandler): this;
|
|
30
|
+
/**
|
|
31
|
+
* 挂载中间件
|
|
32
|
+
* @param middleware 中间件
|
|
33
|
+
* @returns this
|
|
34
|
+
*/
|
|
35
|
+
use(middleware: Middleware): this;
|
|
36
|
+
/**
|
|
37
|
+
* 匹配路由
|
|
38
|
+
* @param pathname 请求路径
|
|
39
|
+
* @returns 匹配结果
|
|
40
|
+
*/
|
|
41
|
+
match(pathname: string): {
|
|
42
|
+
params: Record<string, string | undefined>;
|
|
43
|
+
pathParams: string[];
|
|
44
|
+
} | null;
|
|
45
|
+
/**
|
|
46
|
+
* 执行路由处理器
|
|
47
|
+
* @param session 会话对象
|
|
48
|
+
* @param params 查询参数
|
|
49
|
+
* @param pathParams 路由参数
|
|
50
|
+
*/
|
|
51
|
+
executeHandler(session: Session, params: URLSearchParams, pathParams: string[]): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* 设置可用方法
|
|
54
|
+
* @param methods 可用方法
|
|
55
|
+
* @returns this
|
|
56
|
+
*/
|
|
57
|
+
methods(...methods: string[]): Route;
|
|
58
|
+
}
|