@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/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 chokidar_1 = __importDefault(require("chokidar"));
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 = chokidar_1.default.watch(configPath, {
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
- * 内部函数:尝试加载所有处于 PENDING 状态的插件
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._unloadSinglePlugin(dependentName);
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
- // 更新状态为 PENDING,因为它的配置仍然是启用的
320
- // 如果它被其他插件依赖,当那个插件被卸载时,它也会被重新评估
321
- this.pluginStatus[pluginName] = "pending" /* PluginStatus.PENDING */;
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 core Core 实例
351
- * @param pluginsDir 插件目录,默认为 'plugins'
407
+ * 监听插件文件变化
408
+ * @param pluginName 插件名称
409
+ * @param pluginPath 插件路径
352
410
  */
353
- watchPlugins(core, pluginsDir = 'plugins') {
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 = chokidar_1.default.watch(pluginsDir, {
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
- const pluginName = getPluginNameFromPath(changePath);
372
- if (pluginName) {
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
- const pluginName = getPluginNameFromPath(changePath);
379
- if (pluginName) {
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
- const pluginName = getPluginNameFromPath(changePath);
386
- if (pluginName) {
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
- logger.info(`Watching for plugin changes in ${pluginsDir}`);
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
- * @param name 命令名称
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
@@ -6,6 +6,7 @@
6
6
  export * from './core';
7
7
  export * from './session';
8
8
  export * from './command';
9
+ export * from './route';
9
10
  export * from './config';
10
11
  export * from './logger';
11
12
  export * from './platform';
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
@@ -12,6 +12,7 @@ export declare class Logger {
12
12
  static setCore(core: Core): void;
13
13
  constructor(title: string);
14
14
  private getRandomColor;
15
+ private getTimestamp;
15
16
  private log;
16
17
  info(...args: any[]): void;
17
18
  warn(...args: any[]): void;
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 ansi_colors_1 = __importDefault(require("ansi-colors")); // 引入 ansi-colors
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 = [ansi_colors_1.default.red, ansi_colors_1.default.green, ansi_colors_1.default.yellow, ansi_colors_1.default.blue, ansi_colors_1.default.magenta, ansi_colors_1.default.cyan, ansi_colors_1.default.white, ansi_colors_1.default.gray];
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
- log(level, ...args) {
37
+ getTimestamp() {
35
38
  const now = new Date();
36
- const timestamp = `${now.getFullYear()}/${String(now.getMonth() + 1).padStart(2, '0')}/${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
37
- console.log(`${timestamp} [${level}] ${this.titleColor(this.title)} `, ...args);
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
- error(...args) {
47
- this.log('E', ...args);
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
- }
File without changes
package/dist/platform.js CHANGED
File without changes
@@ -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
+ }