@yumerijs/core 2.2.2 → 3.0.0-alpha.2

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/context.d.ts CHANGED
@@ -1,15 +1,10 @@
1
- import { Core } from './core.js';
1
+ import { Core, Plugin } from './core.js';
2
2
  import { Route } from './route.js';
3
3
  import { HookHandler } from './hook.js';
4
4
  import { Middleware } from './middleware.js';
5
5
  import { IRenderer } from '@yumerijs/types';
6
- interface Plugin {
7
- apply: (ctx: Context, config: any) => any;
8
- disable: (ctx: Context) => Promise<void>;
9
- depend: Array<string>;
10
- provide: Array<string>;
11
- }
12
6
  export interface Components {
7
+ [key: string]: any;
13
8
  }
14
9
  /**
15
10
  * 插件上下文对象
@@ -27,7 +22,7 @@ export declare class Context {
27
22
  private i18ns;
28
23
  component: Components;
29
24
  renderer?: IRenderer;
30
- instance: any;
25
+ module: any;
31
26
  childpath: string;
32
27
  /** 插件名称 */
33
28
  pluginname: string;
@@ -36,7 +31,7 @@ export declare class Context {
36
31
  * @param core Core 实例
37
32
  * @param pluginname 插件名称
38
33
  */
39
- constructor(core: Core, pluginname: string, instance?: any, injections?: Record<string, any>);
34
+ constructor(core: Core, pluginname: string, module?: any, injections?: Record<string, any>);
40
35
  /**
41
36
  * 注入依赖
42
37
  * @param name 依赖名称
@@ -101,10 +96,16 @@ export declare class Context {
101
96
  fork(name?: string, path?: string): Context;
102
97
  /**
103
98
  * 注册子插件
104
- * @param plugin 插件实例
99
+ * @param module 插件模块
100
+ * @param config 插件配置
101
+ */
102
+ apply(module: Plugin, config: any): Promise<void>;
103
+ /**
104
+ * 快速注册子插件(自动 fork)
105
+ * @param module 插件模块
105
106
  * @param config 插件配置
106
107
  */
107
- apply(plugin: Plugin, config: any): Promise<void>;
108
+ plugin(module: Plugin, config?: any): Promise<Context>;
108
109
  /**
109
110
  * 注册 i18n
110
111
  * @param content 内容(可以是嵌套对象或单个key)
@@ -116,4 +117,3 @@ export declare class Context {
116
117
  */
117
118
  dispose(): Promise<void>;
118
119
  }
119
- export {};
package/dist/context.js CHANGED
@@ -16,7 +16,7 @@ export class Context {
16
16
  i18ns = [];
17
17
  component;
18
18
  renderer;
19
- instance;
19
+ module;
20
20
  childpath = '/';
21
21
  /** 插件名称 */
22
22
  pluginname;
@@ -25,9 +25,9 @@ export class Context {
25
25
  * @param core Core 实例
26
26
  * @param pluginname 插件名称
27
27
  */
28
- constructor(core, pluginname, instance, injections = {}) {
28
+ constructor(core, pluginname, module, injections = {}) {
29
29
  this.core = core;
30
- this.instance = instance;
30
+ this.module = module;
31
31
  this.pluginname = pluginname;
32
32
  this.childPlugins = new Map();
33
33
  this.component = injections;
@@ -145,15 +145,28 @@ export class Context {
145
145
  }
146
146
  /**
147
147
  * 注册子插件
148
- * @param plugin 插件实例
148
+ * @param module 插件模块
149
149
  * @param config 插件配置
150
150
  */
151
- async apply(plugin, config) {
152
- if (!plugin || !plugin.apply)
151
+ async apply(module, config) {
152
+ if (!module)
153
153
  return;
154
154
  const ctx = this.fork();
155
- await plugin.apply(ctx, config);
156
- this.childPlugins.set(ctx, plugin);
155
+ await this.core.plugin(module, ctx, config);
156
+ this.childPlugins.set(ctx, module);
157
+ }
158
+ /**
159
+ * 快速注册子插件(自动 fork)
160
+ * @param module 插件模块
161
+ * @param config 插件配置
162
+ */
163
+ async plugin(module, config = {}) {
164
+ if (!module)
165
+ return;
166
+ const ctx = this.fork();
167
+ await this.core.plugin(module, ctx, config);
168
+ this.childPlugins.set(ctx, module);
169
+ return ctx;
157
170
  }
158
171
  /**
159
172
  * 注册 i18n
package/dist/core.d.ts CHANGED
@@ -9,19 +9,32 @@ import { HookHandler, Hook } from './hook.js';
9
9
  import { Server as CoreServer } from './server.js';
10
10
  import { I18n } from './i18n.js';
11
11
  import { IRenderer } from '@yumerijs/types';
12
- interface Plugin {
13
- apply: (ctx: Context, config: Config) => Promise<void>;
14
- disable: (ctx: Context) => Promise<void>;
15
- depend: Array<string>;
16
- provide: Array<string>;
12
+ export interface Plugin {
13
+ apply?: (ctx: Context, config: Config) => Promise<void> | void;
14
+ disable?: (ctx: Context) => Promise<void> | void;
15
+ depend?: Array<string>;
16
+ provide?: Array<string>;
17
+ render?: string;
18
+ config?: Schema<any>;
17
19
  }
20
+ type PluginConstructor = new (ctx: Context, config: Config) => Plugin;
21
+ type PluginModuleLike = Plugin | PluginConstructor | ((ctx: Context, config: Config) => Promise<void> | void) | {
22
+ default?: Plugin | PluginConstructor | ((ctx: Context, config: Config) => Promise<void> | void);
23
+ apply?: ((ctx: Context, config: Config) => Promise<void> | void);
24
+ disable?: (ctx: Context) => Promise<void> | void;
25
+ depend?: Array<string>;
26
+ provide?: Array<string>;
27
+ render?: string;
28
+ config?: Schema<any>;
29
+ };
30
+ export declare function resolvePluginModule(module: PluginModuleLike, context: Context, config: Config): Plugin;
18
31
  export interface CoreOptions {
19
- port: number;
20
- host: string;
21
- staticDir: string;
22
- enableCors: boolean;
23
- enableWs: boolean;
24
- lang: string[];
32
+ port?: number;
33
+ host?: string;
34
+ enableCors?: boolean;
35
+ enableWs?: boolean;
36
+ lang?: string[];
37
+ skipcheckUpdates?: boolean;
25
38
  }
26
39
  export declare const coreConfigSchema: Schema<CoreOptions>;
27
40
  export declare const enum PluginStatus {
@@ -44,12 +57,13 @@ export declare class Core {
44
57
  loader: any;
45
58
  renderers: Map<string, IRenderer>;
46
59
  pluginRenderers: Map<string, string>;
47
- constructor(loader?: any, coreConfig?: CoreOptions, setCore?: boolean);
60
+ constructor(loader?: any, coreConfig?: CoreOptions, loggersetCore?: boolean, splash?: boolean);
61
+ checkUpdate(): Promise<void>;
48
62
  addRenderer(renderer: IRenderer): void;
49
63
  getRendererForPlugin(pluginName: string): string | undefined;
50
64
  runCore(): Promise<void>;
51
65
  getShortPluginName(pluginName: string): string;
52
- plugin(pluginInstance: Plugin, context: Context, config: Config): Promise<void>;
66
+ plugin(module: PluginModuleLike, context: Context, config: Config): Promise<void>;
53
67
  registerComponent(name: string, component: any): void;
54
68
  getComponent(name: string): any;
55
69
  unregisterComponent(name: string): void;
package/dist/core.js CHANGED
@@ -1,16 +1,60 @@
1
1
  import { EventEmitter } from 'events';
2
- import { Schema } from './config.js';
2
+ import { Schema, fallback } from './config.js';
3
3
  import { Logger } from './logger.js';
4
4
  import { Route } from './route.js';
5
5
  import { Hook } from './hook.js';
6
6
  import { Server as CoreServer } from './server.js';
7
+ import * as fs from 'fs';
8
+ const version = JSON.parse(await fs.promises.readFile(new URL('../package.json', import.meta.url), 'utf-8')).version;
9
+ function isClassPlugin(value) {
10
+ if (typeof value !== 'function')
11
+ return false;
12
+ const source = Function.prototype.toString.call(value);
13
+ return source.startsWith('class ');
14
+ }
15
+ function mergePluginMeta(target, source) {
16
+ if (!source || typeof source !== 'object')
17
+ return target;
18
+ if (target.depend == null && Array.isArray(source.depend))
19
+ target.depend = source.depend;
20
+ if (target.provide == null && Array.isArray(source.provide))
21
+ target.provide = source.provide;
22
+ if (target.render == null && typeof source.render === 'string')
23
+ target.render = source.render;
24
+ if (target.config == null && source.config)
25
+ target.config = source.config;
26
+ if (target.disable == null && typeof source.disable === 'function')
27
+ target.disable = source.disable.bind(source);
28
+ return target;
29
+ }
30
+ export function resolvePluginModule(module, context, config) {
31
+ const candidate = module?.default ?? module?.apply ?? module;
32
+ let plugin;
33
+ if (isClassPlugin(candidate)) {
34
+ plugin = new candidate(context, config);
35
+ }
36
+ else if (typeof candidate === 'function') {
37
+ plugin = { apply: candidate };
38
+ }
39
+ else if (candidate && typeof candidate === 'object') {
40
+ plugin = candidate;
41
+ }
42
+ else {
43
+ throw new TypeError('Invalid plugin module. Expected class, function, or plugin object.');
44
+ }
45
+ mergePluginMeta(plugin, module);
46
+ if (candidate !== module) {
47
+ mergePluginMeta(plugin, candidate);
48
+ }
49
+ return plugin;
50
+ }
7
51
  export const coreConfigSchema = Schema.object({
8
52
  port: Schema.number('监听端口').default(14510),
9
53
  host: Schema.string('监听地址').default('0.0.0.0'),
10
- staticDir: Schema.string('静态目录').default('public'),
11
54
  enableCors: Schema.boolean('启用跨域').default(false),
12
55
  enableWs: Schema.boolean('启用 WebSocket').default(false),
13
56
  lang: Schema.array(Schema.string(), '语言列表').default(['zh', 'en']),
57
+ skipcheckUpdates: Schema.boolean('启动时检查更新').default(false)
14
58
  });
15
59
  export class Core {
16
60
  emitter = new EventEmitter();
@@ -25,12 +69,32 @@ export class Core {
25
69
  loader;
26
70
  renderers = new Map();
27
71
  pluginRenderers = new Map(); // Stores which plugin uses which renderer
28
- constructor(loader, coreConfig, setCore = true) {
29
- this.coreConfig = coreConfig || {};
72
+ constructor(loader, coreConfig = {}, loggersetCore = true, splash = true) {
73
+ this.coreConfig = fallback(coreConfigSchema, coreConfig);
30
74
  this.loader = loader;
31
- if (setCore)
75
+ if (splash)
76
+ this.logger.info('Welcome to use Yumeri ver.' + version);
77
+ if (!this.coreConfig.skipcheckUpdates)
78
+ this.checkUpdate();
79
+ if (loggersetCore)
32
80
  Logger.setCore(this);
33
81
  }
82
+ async checkUpdate() {
83
+ try {
84
+ const response = await fetch('https://registry.npmjs.org/yumeri/latest');
85
+ if (!response.ok)
86
+ return;
87
+ const { version: latest } = await response.json();
88
+ if (latest !== version) {
89
+ this.logger.info(`There is new version for Yumeri: ${version} -> ${latest}`);
90
+ }
91
+ else {
92
+ }
93
+ }
94
+ catch (error) {
95
+ this.logger.error('Error while checking updates: ', error.message);
96
+ }
97
+ }
34
98
  addRenderer(renderer) {
35
99
  if (this.renderers.has(renderer.name)) {
36
100
  this.logger.warn(`Renderer "${renderer.name}" is already registered and will be overwritten.`);
@@ -46,7 +110,6 @@ export class Core {
46
110
  host: this.coreConfig.host || '0.0.0.0',
47
111
  enableCors: this.coreConfig.enableCors || false,
48
112
  enableWs: this.coreConfig.enableWs || false,
49
- staticDir: this.coreConfig.staticDir || 'public',
50
113
  });
51
114
  await this.server.start();
52
115
  }
@@ -60,12 +123,21 @@ export class Core {
60
123
  }
61
124
  return pluginName;
62
125
  }
63
- async plugin(pluginInstance, context, config) {
126
+ async plugin(module, context, config) {
127
+ const plugin = resolvePluginModule(module, context, config);
64
128
  const shortName = this.getShortPluginName(context.pluginname);
65
- context.instance = pluginInstance;
129
+ context.module = plugin;
130
+ // 自动依赖注入
131
+ const depend = plugin.depend || [];
132
+ for (const name of depend) {
133
+ const component = this.getComponent(name);
134
+ if (component) {
135
+ context.inject(name, component);
136
+ }
137
+ }
66
138
  this.logger.info(`apply plugin ${shortName}`);
67
- if (pluginInstance.apply) {
68
- await pluginInstance.apply(context, config);
139
+ if (plugin.apply) {
140
+ await plugin.apply(context, config);
69
141
  }
70
142
  }
71
143
  registerComponent(name, component) {
@@ -123,7 +195,7 @@ export class Core {
123
195
  async executeRoute(pathname, session, queryParams) {
124
196
  for (const routePath in this.routes) {
125
197
  const route = this.routes[routePath];
126
- const result = route.match(pathname);
198
+ const result = route.match(pathname, session.client?.headers?.host);
127
199
  if (result) {
128
200
  try {
129
201
  this.emit('request:start', {
@@ -151,7 +223,7 @@ export class Core {
151
223
  }
152
224
  else {
153
225
  const start = Date.now();
154
- await route.executeHandler(session, queryParams, result.pathParams);
226
+ await route.executeHandler(session, queryParams, result.pathParams, result.hostParams);
155
227
  this.emit('route:end', {
156
228
  path: pathname,
157
229
  route: routePath,
package/dist/route.d.ts CHANGED
@@ -25,6 +25,7 @@ export declare class Route {
25
25
  allowedMethods: string[];
26
26
  ws: WebSocketServer;
27
27
  context: Context;
28
+ private routehost;
28
29
  /**
29
30
  * 创建路由
30
31
  * @param path 路由路径
@@ -37,6 +38,12 @@ export declare class Route {
37
38
  * @returns this
38
39
  */
39
40
  action(handler: RouteHandler): this;
41
+ /**
42
+ * 设置允许的host
43
+ * @param host host列表
44
+ * @returns this
45
+ */
46
+ host(host: string[] | string): this;
40
47
  /**
41
48
  * 挂载中间件
42
49
  * @param middleware 中间件
@@ -46,11 +53,13 @@ export declare class Route {
46
53
  /**
47
54
  * 匹配路由
48
55
  * @param pathname 请求路径
56
+ * @param host 请求host
49
57
  * @returns 匹配结果
50
58
  */
51
- match(pathname: string): {
59
+ match(pathname: string, host?: string): {
52
60
  params: Record<string, string | undefined>;
53
61
  pathParams: string[];
62
+ hostParams: Record<string, string>;
54
63
  } | null;
55
64
  /**
56
65
  * 执行路由处理器
@@ -58,7 +67,7 @@ export declare class Route {
58
67
  * @param params 查询参数
59
68
  * @param pathParams 路由参数
60
69
  */
61
- executeHandler(session: Session, params: URLSearchParams, pathParams: string[]): Promise<void>;
70
+ executeHandler(session: Session, params: URLSearchParams, pathParams: string[], hostParams: Record<string, string>): Promise<void>;
62
71
  /**
63
72
  * 设置可用方法
64
73
  * @param methods 可用方法
package/dist/route.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { WebSocketServer } from 'ws';
2
+ import { isIP } from 'node:net';
2
3
  function parsePatternToSegments(pattern) {
3
4
  // Normalize: remove leading/trailing slashes for consistent splitting
4
5
  const norm = pattern.replace(/^\/+|\/+$/g, '');
@@ -22,6 +23,196 @@ function parsePatternToSegments(pattern) {
22
23
  }
23
24
  return { segments, params };
24
25
  }
26
+ function parseSegmentToken(part) {
27
+ if (!part.startsWith(':')) {
28
+ return { type: 'static', value: part };
29
+ }
30
+ let name = part.slice(1);
31
+ let modifier = '';
32
+ const lastChar = name[name.length - 1];
33
+ if (lastChar === '?' || lastChar === '*' || lastChar === '+') {
34
+ modifier = lastChar;
35
+ name = name.slice(0, -1);
36
+ }
37
+ return { type: 'param', name, modifier };
38
+ }
39
+ function splitHostAndPortPattern(pattern) {
40
+ const normalized = pattern.trim().toLowerCase();
41
+ if (normalized.startsWith('[')) {
42
+ const end = normalized.indexOf(']');
43
+ if (end === -1)
44
+ return { hostPart: normalized };
45
+ const hostPart = normalized.slice(1, end);
46
+ const rest = normalized.slice(end + 1);
47
+ if (rest.startsWith(':')) {
48
+ return { hostPart, portPart: rest.slice(1) };
49
+ }
50
+ return { hostPart };
51
+ }
52
+ const colonCount = (normalized.match(/:/g) || []).length;
53
+ const lastColon = normalized.lastIndexOf(':');
54
+ const hasDots = normalized.includes('.');
55
+ if (lastColon === -1) {
56
+ return { hostPart: normalized };
57
+ }
58
+ const suffix = normalized.slice(lastColon + 1);
59
+ const prefix = normalized.slice(0, lastColon);
60
+ const isPortToken = /^\d+$/.test(suffix) || /^:[a-z_][a-z0-9_]*[?*+]?$/.test(`:${suffix}`);
61
+ if (!isPortToken) {
62
+ return { hostPart: normalized };
63
+ }
64
+ if (normalized.startsWith(':') && !hasDots) {
65
+ const paramBody = prefix.slice(1);
66
+ if (/^[a-z_][a-z0-9_]*$/.test(paramBody)) {
67
+ return { hostPart: prefix, portPart: suffix };
68
+ }
69
+ }
70
+ if (colonCount === 1 && !normalized.startsWith(':')) {
71
+ return { hostPart: prefix, portPart: suffix };
72
+ }
73
+ if (hasDots) {
74
+ return { hostPart: prefix, portPart: suffix };
75
+ }
76
+ return { hostPart: normalized };
77
+ }
78
+ function parseHostPattern(hostPattern) {
79
+ const { hostPart, portPart } = splitHostAndPortPattern(hostPattern);
80
+ const matchWholeHost = !hostPart.includes('.') || hostPart.includes(':');
81
+ const rawParts = matchWholeHost ? [hostPart] : hostPart.split('.');
82
+ return {
83
+ hostSegments: rawParts.map(parseSegmentToken),
84
+ portSegment: portPart ? parseSegmentToken(portPart) : undefined,
85
+ matchWholeHost,
86
+ };
87
+ }
88
+ function parseAuthority(host) {
89
+ const normalized = host.trim().toLowerCase();
90
+ if (normalized.startsWith('[')) {
91
+ const end = normalized.indexOf(']');
92
+ if (end !== -1) {
93
+ const hostname = normalized.slice(1, end);
94
+ const rest = normalized.slice(end + 1);
95
+ const port = rest.startsWith(':') ? rest.slice(1) : undefined;
96
+ return { hostname, port, kind: 'ipv6' };
97
+ }
98
+ }
99
+ if (isIP(normalized) === 6) {
100
+ return { hostname: normalized, kind: 'ipv6' };
101
+ }
102
+ const lastColon = normalized.lastIndexOf(':');
103
+ if (lastColon !== -1 && normalized.indexOf(':') === lastColon) {
104
+ const maybePort = normalized.slice(lastColon + 1);
105
+ if (/^\d+$/.test(maybePort)) {
106
+ const hostname = normalized.slice(0, lastColon);
107
+ if (isIP(hostname) === 4) {
108
+ return { hostname, port: maybePort, kind: 'ipv4' };
109
+ }
110
+ return { hostname, port: maybePort, kind: 'domain' };
111
+ }
112
+ }
113
+ if (isIP(normalized) === 4) {
114
+ return { hostname: normalized, kind: 'ipv4' };
115
+ }
116
+ return { hostname: normalized, kind: 'domain' };
117
+ }
118
+ function matchSingleSegment(seg, value, params) {
119
+ if (seg.type === 'static') {
120
+ return value === seg.value;
121
+ }
122
+ switch (seg.modifier) {
123
+ case '':
124
+ case '+':
125
+ case '*':
126
+ if (value == null || value === '') {
127
+ if (seg.modifier === '*') {
128
+ params[seg.name] = undefined;
129
+ return true;
130
+ }
131
+ return false;
132
+ }
133
+ params[seg.name] = value;
134
+ return true;
135
+ case '?':
136
+ params[seg.name] = value || undefined;
137
+ return true;
138
+ default:
139
+ return false;
140
+ }
141
+ }
142
+ function matchHostSegments(segments, parts, joiner, params) {
143
+ let hi = 0;
144
+ let hj = 0;
145
+ while (hi < segments.length) {
146
+ const seg = segments[hi];
147
+ const nextSegIsStatic = !!segments[hi + 1] && segments[hi + 1].type === 'static';
148
+ if (seg.type === 'static') {
149
+ if (hj >= parts.length || parts[hj] !== seg.value)
150
+ return false;
151
+ hi++;
152
+ hj++;
153
+ continue;
154
+ }
155
+ switch (seg.modifier) {
156
+ case '':
157
+ if (hj >= parts.length)
158
+ return false;
159
+ params[seg.name] = parts[hj];
160
+ hi++;
161
+ hj++;
162
+ break;
163
+ case '?':
164
+ if (hj < parts.length) {
165
+ if (nextSegIsStatic && parts[hj] === segments[hi + 1].value) {
166
+ params[seg.name] = undefined;
167
+ hi++;
168
+ }
169
+ else {
170
+ params[seg.name] = parts[hj];
171
+ hi++;
172
+ hj++;
173
+ }
174
+ }
175
+ else {
176
+ params[seg.name] = undefined;
177
+ hi++;
178
+ }
179
+ break;
180
+ case '+': {
181
+ if (hj >= parts.length)
182
+ return false;
183
+ let end = parts.length;
184
+ if (nextSegIsStatic) {
185
+ const nextStatic = segments[hi + 1].value;
186
+ const found = parts.indexOf(nextStatic, hj);
187
+ if (found === -1 || found === hj)
188
+ return false;
189
+ end = found;
190
+ }
191
+ params[seg.name] = parts.slice(hj, end).join(joiner);
192
+ hj = end;
193
+ hi++;
194
+ break;
195
+ }
196
+ case '*': {
197
+ let end = parts.length;
198
+ if (nextSegIsStatic) {
199
+ const nextStatic = segments[hi + 1].value;
200
+ const found = parts.indexOf(nextStatic, hj);
201
+ if (found !== -1)
202
+ end = found;
203
+ }
204
+ const value = parts.slice(hj, end).join(joiner);
205
+ params[seg.name] = value || undefined;
206
+ hj = end;
207
+ hi++;
208
+ break;
209
+ }
210
+ default:
211
+ return false;
212
+ }
213
+ }
214
+ return hj === parts.length;
215
+ }
25
216
  /**
26
217
  * Route class using segment-based matching (no fragile capture-group-index mapping).
27
218
  * Behavior matches the SimpleRouter in the webpage:
@@ -39,6 +230,7 @@ export class Route {
39
230
  allowedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'];
40
231
  ws = null;
41
232
  context;
233
+ routehost = null;
42
234
  /**
43
235
  * 创建路由
44
236
  * @param path 路由路径
@@ -60,6 +252,20 @@ export class Route {
60
252
  this.handler = handler;
61
253
  return this;
62
254
  }
255
+ /**
256
+ * 设置允许的host
257
+ * @param host host列表
258
+ * @returns this
259
+ */
260
+ host(host) {
261
+ if (typeof host === 'string') {
262
+ this.routehost = [host];
263
+ }
264
+ else {
265
+ this.routehost = host;
266
+ }
267
+ return this;
268
+ }
63
269
  /**
64
270
  * 挂载中间件
65
271
  * @param middleware 中间件
@@ -72,9 +278,41 @@ export class Route {
72
278
  /**
73
279
  * 匹配路由
74
280
  * @param pathname 请求路径
281
+ * @param host 请求host
75
282
  * @returns 匹配结果
76
283
  */
77
- match(pathname) {
284
+ match(pathname, host) {
285
+ let hostParams = {};
286
+ // Host 匹配逻辑开始
287
+ if (this.routehost && this.routehost.length > 0) {
288
+ if (!host)
289
+ return null; // 如果设置了 host 限制但没有传入 host,直接失败
290
+ const actual = parseAuthority(host);
291
+ let hostMatched = false;
292
+ for (const pattern of this.routehost) {
293
+ const parsedPattern = parseHostPattern(pattern);
294
+ const hostParts = parsedPattern.matchWholeHost || actual.kind === 'ipv6'
295
+ ? [actual.hostname]
296
+ : actual.hostname.split('.');
297
+ const joiner = parsedPattern.matchWholeHost || actual.kind === 'ipv6' ? ':' : '.';
298
+ let tempParams = {};
299
+ const hostOk = matchHostSegments(parsedPattern.hostSegments, hostParts, joiner, tempParams);
300
+ if (!hostOk)
301
+ continue;
302
+ if (parsedPattern.portSegment) {
303
+ const portOk = matchSingleSegment(parsedPattern.portSegment, actual.port, tempParams);
304
+ if (!portOk)
305
+ continue;
306
+ }
307
+ hostMatched = true;
308
+ hostParams = tempParams;
309
+ break; // 只要匹配到一个 host pattern 就行
310
+ }
311
+ if (!hostMatched)
312
+ return null;
313
+ }
314
+ // Host 匹配逻辑结束
315
+ // 路由匹配逻辑开始
78
316
  if (pathname.startsWith('/')) {
79
317
  // normalize pathname: remove leading/trailing slashes
80
318
  const normPath = pathname.replace(/^\/+|\/+$/g, '');
@@ -198,10 +436,12 @@ export class Route {
198
436
  // after finishing pattern, path must be fully consumed (no extra segments)
199
437
  if (j !== parts.length)
200
438
  return null;
201
- return { params, pathParams };
439
+ const result = { params, pathParams, hostParams };
440
+ return result;
202
441
  }
203
442
  else if (pathname.startsWith('root') && this.path == pathname) {
204
- return { params: { pathname }, pathParams: [pathname] };
443
+ const result = { params: { pathname }, pathParams: [pathname], hostParams };
444
+ return result;
205
445
  }
206
446
  return null;
207
447
  }
@@ -211,9 +451,9 @@ export class Route {
211
451
  * @param params 查询参数
212
452
  * @param pathParams 路由参数
213
453
  */
214
- async executeHandler(session, params, pathParams) {
454
+ async executeHandler(session, params, pathParams, hostParams) {
215
455
  if (this.handler) {
216
- await this.handler(session, params, ...pathParams);
456
+ await this.handler(session, params, ...pathParams, ...Object.values(hostParams));
217
457
  }
218
458
  }
219
459
  /**