@yumerijs/core 2.0.14 → 2.1.1
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/config.d.ts +27 -118
- package/dist/config.js +68 -136
- package/dist/context.d.ts +4 -4
- package/dist/context.js +6 -7
- package/dist/core.d.ts +10 -5
- package/dist/core.js +76 -20
- package/dist/i18n.d.ts +1 -1
- package/dist/i18n.js +3 -2
- package/dist/route.d.ts +4 -1
- package/dist/route.js +4 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.js +21 -4
- package/dist/session.d.ts +18 -1
- package/dist/session.js +62 -1
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -1,122 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* 插件配置模式定义类
|
|
8
|
-
* 用于描述插件配置项的类型、默认值、描述等信息
|
|
9
|
-
*/
|
|
10
|
-
export declare class ConfigSchema {
|
|
11
|
-
/**
|
|
12
|
-
* 配置项类型
|
|
13
|
-
*/
|
|
14
|
-
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
|
|
15
|
-
/**
|
|
16
|
-
* 默认值
|
|
17
|
-
*/
|
|
18
|
-
default?: any;
|
|
19
|
-
/**
|
|
20
|
-
* 配置项描述
|
|
21
|
-
*/
|
|
1
|
+
export declare function fallback<T>(schema: Schema<T>, config: T): T;
|
|
2
|
+
export declare class Schema<T = any> {
|
|
3
|
+
_type?: T;
|
|
4
|
+
type: string;
|
|
5
|
+
isRequired?: boolean;
|
|
22
6
|
description?: string;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
*/
|
|
30
|
-
enum?: any[];
|
|
31
|
-
/**
|
|
32
|
-
* 数组项类型定义(当type为array时使用)
|
|
33
|
-
*/
|
|
34
|
-
items?: ConfigSchema;
|
|
35
|
-
/**
|
|
36
|
-
* 对象属性定义(当type为object时使用)
|
|
37
|
-
*/
|
|
38
|
-
properties?: Record<string, ConfigSchema>;
|
|
39
|
-
/**
|
|
40
|
-
* 创建配置模式对象
|
|
41
|
-
* @param type 配置项类型
|
|
42
|
-
* @param options 配置项选项
|
|
43
|
-
*/
|
|
44
|
-
constructor(type: 'string' | 'number' | 'boolean' | 'object' | 'array', options?: {
|
|
45
|
-
default?: any;
|
|
46
|
-
description?: string;
|
|
47
|
-
required?: boolean;
|
|
48
|
-
enum?: any[];
|
|
49
|
-
items?: ConfigSchema;
|
|
50
|
-
properties?: Record<string, ConfigSchema>;
|
|
7
|
+
defaultValue?: any;
|
|
8
|
+
properties?: Record<string, Schema<any>>;
|
|
9
|
+
items?: Schema<any>;
|
|
10
|
+
enum?: T[];
|
|
11
|
+
constructor(definition: Omit<Schema<T>, '_type' | 'required' | 'default'> & {
|
|
12
|
+
enum?: T[];
|
|
51
13
|
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
* 创建布尔类型配置模式
|
|
66
|
-
* @param options 配置项选项
|
|
67
|
-
* @returns 配置模式对象
|
|
68
|
-
*/
|
|
69
|
-
static boolean(options?: Omit<ConfigSchema, 'type' | 'items' | 'properties'>): ConfigSchema;
|
|
70
|
-
/**
|
|
71
|
-
* 创建对象类型配置模式
|
|
72
|
-
* @param properties 对象属性定义
|
|
73
|
-
* @param options 配置项选项
|
|
74
|
-
* @returns 配置模式对象
|
|
75
|
-
*/
|
|
76
|
-
static object(properties: Record<string, ConfigSchema>, options?: Omit<ConfigSchema, 'type' | 'items' | 'properties'>): ConfigSchema;
|
|
77
|
-
/**
|
|
78
|
-
* 创建数组类型配置模式
|
|
79
|
-
* @param items 数组项类型定义
|
|
80
|
-
* @param options 配置项选项
|
|
81
|
-
* @returns 配置模式对象
|
|
82
|
-
*/
|
|
83
|
-
static array(items: ConfigSchema, options?: Omit<ConfigSchema, 'type' | 'items' | 'properties'>): ConfigSchema;
|
|
14
|
+
static string(description?: string): Schema<string>;
|
|
15
|
+
static number(description?: string): Schema<number>;
|
|
16
|
+
static boolean(description?: string): Schema<boolean>;
|
|
17
|
+
static array<T>(inner: Schema<T>, description?: string): Schema<T[]>;
|
|
18
|
+
static object<T extends {}>(properties: {
|
|
19
|
+
[K in keyof T]: Schema<T[K]>;
|
|
20
|
+
}, description?: string): Schema<T>;
|
|
21
|
+
static extend<T extends {}, U extends {}>(base: Schema<T>, extension: {
|
|
22
|
+
[K in keyof U]: Schema<U[K]>;
|
|
23
|
+
}, description?: string): Schema<T & U>;
|
|
24
|
+
static enum<L extends string | number>(values: L[], description?: string): Schema<L>;
|
|
25
|
+
required(this: this): this;
|
|
26
|
+
default(this: this, value: T): this;
|
|
84
27
|
}
|
|
85
|
-
export
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
*/
|
|
89
|
-
name: string;
|
|
90
|
-
/**
|
|
91
|
-
* 配置内容
|
|
92
|
-
*/
|
|
93
|
-
content: {
|
|
94
|
-
[name: string]: any;
|
|
95
|
-
};
|
|
96
|
-
/**
|
|
97
|
-
* 配置模式
|
|
98
|
-
*/
|
|
99
|
-
schema?: Record<string, ConfigSchema>;
|
|
100
|
-
/**
|
|
101
|
-
* 创建配置对象
|
|
102
|
-
* @param name 配置名称
|
|
103
|
-
* @param content 配置内容
|
|
104
|
-
* @param schema 配置模式
|
|
105
|
-
*/
|
|
106
|
-
constructor(name: string, content?: {
|
|
107
|
-
[name: string]: any;
|
|
108
|
-
}, schema?: Record<string, ConfigSchema>);
|
|
109
|
-
/**
|
|
110
|
-
* 获取配置项值
|
|
111
|
-
* @param key 配置项键名
|
|
112
|
-
* @param defaultValue 默认值
|
|
113
|
-
* @returns 配置项值
|
|
114
|
-
*/
|
|
115
|
-
get<T>(key: string, defaultValue?: T): T;
|
|
116
|
-
/**
|
|
117
|
-
* 设置配置项值
|
|
118
|
-
* @param key 配置项键名
|
|
119
|
-
* @param value 配置项值
|
|
120
|
-
*/
|
|
121
|
-
set(key: string, value: any): void;
|
|
28
|
+
export { Schema as ConfigSchema };
|
|
29
|
+
export interface Config {
|
|
30
|
+
[key: string]: any;
|
|
122
31
|
}
|
package/dist/config.js
CHANGED
|
@@ -1,151 +1,83 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @time: 2025/05/24 12:18
|
|
4
|
-
* @author: FireGuo
|
|
5
|
-
* WindyPear-Team All right reserved
|
|
6
|
-
**/
|
|
7
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
3
|
+
exports.ConfigSchema = exports.Schema = void 0;
|
|
4
|
+
exports.fallback = fallback;
|
|
5
|
+
function isNullable(value) {
|
|
6
|
+
return value === null || value === undefined;
|
|
7
|
+
}
|
|
8
|
+
function fallback(schema, config) {
|
|
9
|
+
if (!schema)
|
|
10
|
+
return config;
|
|
11
|
+
let result = config;
|
|
12
|
+
if (isNullable(result)) {
|
|
13
|
+
result = schema.defaultValue;
|
|
14
|
+
}
|
|
15
|
+
if (schema.type === 'object') {
|
|
16
|
+
if (typeof result !== 'object' || result === null) {
|
|
17
|
+
result = {};
|
|
18
|
+
}
|
|
19
|
+
for (const key in schema.properties) {
|
|
20
|
+
const innerSchema = schema.properties[key];
|
|
21
|
+
result[key] = fallback(innerSchema, result[key]);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else if (schema.type === 'array' && schema.items) {
|
|
25
|
+
if (!Array.isArray(result)) {
|
|
26
|
+
result = [];
|
|
27
|
+
}
|
|
28
|
+
result = result.map((item) => fallback(schema.items, item));
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
class Schema {
|
|
33
|
+
_type; // Phantom type
|
|
17
34
|
type;
|
|
18
|
-
|
|
19
|
-
* 默认值
|
|
20
|
-
*/
|
|
21
|
-
default;
|
|
22
|
-
/**
|
|
23
|
-
* 配置项描述
|
|
24
|
-
*/
|
|
35
|
+
isRequired;
|
|
25
36
|
description;
|
|
26
|
-
|
|
27
|
-
* 是否必需
|
|
28
|
-
*/
|
|
29
|
-
required;
|
|
30
|
-
/**
|
|
31
|
-
* 枚举值列表(可选项)
|
|
32
|
-
*/
|
|
33
|
-
enum;
|
|
34
|
-
/**
|
|
35
|
-
* 数组项类型定义(当type为array时使用)
|
|
36
|
-
*/
|
|
37
|
-
items;
|
|
38
|
-
/**
|
|
39
|
-
* 对象属性定义(当type为object时使用)
|
|
40
|
-
*/
|
|
37
|
+
defaultValue;
|
|
41
38
|
properties;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
this.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.required = options.required;
|
|
53
|
-
this.enum = options.enum;
|
|
54
|
-
this.items = options.items;
|
|
55
|
-
this.properties = options.properties;
|
|
56
|
-
}
|
|
39
|
+
items;
|
|
40
|
+
enum;
|
|
41
|
+
constructor(definition) {
|
|
42
|
+
this.type = definition.type;
|
|
43
|
+
this.isRequired = definition.isRequired;
|
|
44
|
+
this.description = definition.description;
|
|
45
|
+
this.defaultValue = definition.defaultValue;
|
|
46
|
+
this.properties = definition.properties;
|
|
47
|
+
this.items = definition.items;
|
|
48
|
+
this.enum = definition.enum;
|
|
57
49
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
* @param options 配置项选项
|
|
61
|
-
* @returns 配置模式对象
|
|
62
|
-
*/
|
|
63
|
-
static string(options) {
|
|
64
|
-
return new ConfigSchema('string', options);
|
|
50
|
+
static string(description) {
|
|
51
|
+
return new Schema({ type: 'string', description });
|
|
65
52
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
* @param options 配置项选项
|
|
69
|
-
* @returns 配置模式对象
|
|
70
|
-
*/
|
|
71
|
-
static number(options) {
|
|
72
|
-
return new ConfigSchema('number', options);
|
|
53
|
+
static number(description) {
|
|
54
|
+
return new Schema({ type: 'number', description });
|
|
73
55
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
* @param options 配置项选项
|
|
77
|
-
* @returns 配置模式对象
|
|
78
|
-
*/
|
|
79
|
-
static boolean(options) {
|
|
80
|
-
return new ConfigSchema('boolean', options);
|
|
56
|
+
static boolean(description) {
|
|
57
|
+
return new Schema({ type: 'boolean', description });
|
|
81
58
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
* @param properties 对象属性定义
|
|
85
|
-
* @param options 配置项选项
|
|
86
|
-
* @returns 配置模式对象
|
|
87
|
-
*/
|
|
88
|
-
static object(properties, options) {
|
|
89
|
-
return new ConfigSchema('object', { ...options, properties });
|
|
59
|
+
static array(inner, description) {
|
|
60
|
+
return new Schema({ type: 'array', items: inner, description });
|
|
90
61
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
* @param items 数组项类型定义
|
|
94
|
-
* @param options 配置项选项
|
|
95
|
-
* @returns 配置模式对象
|
|
96
|
-
*/
|
|
97
|
-
static array(items, options) {
|
|
98
|
-
return new ConfigSchema('array', { ...options, items });
|
|
62
|
+
static object(properties, description) {
|
|
63
|
+
return new Schema({ type: 'object', properties, description });
|
|
99
64
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* 配置名称
|
|
105
|
-
*/
|
|
106
|
-
name = '';
|
|
107
|
-
/**
|
|
108
|
-
* 配置内容
|
|
109
|
-
*/
|
|
110
|
-
content = {};
|
|
111
|
-
/**
|
|
112
|
-
* 配置模式
|
|
113
|
-
*/
|
|
114
|
-
schema;
|
|
115
|
-
/**
|
|
116
|
-
* 创建配置对象
|
|
117
|
-
* @param name 配置名称
|
|
118
|
-
* @param content 配置内容
|
|
119
|
-
* @param schema 配置模式
|
|
120
|
-
*/
|
|
121
|
-
constructor(name, content, schema) {
|
|
122
|
-
this.name = name;
|
|
123
|
-
this.content = content || {}; // 如果 content 是 undefined,则赋值为空对象
|
|
124
|
-
this.schema = schema;
|
|
65
|
+
static extend(base, extension, description) {
|
|
66
|
+
const combinedProperties = { ...base.properties, ...extension };
|
|
67
|
+
return new Schema({ type: 'object', properties: combinedProperties, description: description || base.description });
|
|
125
68
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (this.content[key] !== undefined) {
|
|
134
|
-
return this.content[key];
|
|
135
|
-
}
|
|
136
|
-
// 如果有schema,尝试从schema中获取默认值
|
|
137
|
-
if (this.schema && this.schema[key] && this.schema[key].default !== undefined) {
|
|
138
|
-
return this.schema[key].default;
|
|
139
|
-
}
|
|
140
|
-
return defaultValue;
|
|
69
|
+
static enum(values, description) {
|
|
70
|
+
const type = typeof values[0] === 'string' ? 'string' : typeof values[0] === 'number' ? 'number' : 'string'; // Infer type based on first value
|
|
71
|
+
return new Schema({ type, enum: values, description });
|
|
72
|
+
}
|
|
73
|
+
required() {
|
|
74
|
+
this.isRequired = true;
|
|
75
|
+
return this;
|
|
141
76
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
* @param value 配置项值
|
|
146
|
-
*/
|
|
147
|
-
set(key, value) {
|
|
148
|
-
this.content[key] = value;
|
|
77
|
+
default(value) {
|
|
78
|
+
this.defaultValue = value;
|
|
79
|
+
return this;
|
|
149
80
|
}
|
|
150
81
|
}
|
|
151
|
-
exports.
|
|
82
|
+
exports.Schema = Schema;
|
|
83
|
+
exports.ConfigSchema = Schema;
|
package/dist/context.d.ts
CHANGED
|
@@ -2,9 +2,8 @@ import { Core } from './core';
|
|
|
2
2
|
import { Route } from './route';
|
|
3
3
|
import { HookHandler } from './hook';
|
|
4
4
|
import { Middleware } from './middleware';
|
|
5
|
-
import { Config } from './config';
|
|
6
5
|
interface Plugin {
|
|
7
|
-
apply: (ctx: Context, config:
|
|
6
|
+
apply: (ctx: Context, config: any) => any;
|
|
8
7
|
disable: (ctx: Context) => Promise<void>;
|
|
9
8
|
depend: Array<string>;
|
|
10
9
|
provide: Array<string>;
|
|
@@ -26,6 +25,7 @@ export declare class Context {
|
|
|
26
25
|
private childPlugins;
|
|
27
26
|
private i18ns;
|
|
28
27
|
component: Components;
|
|
28
|
+
instance: any;
|
|
29
29
|
/** 插件名称 */
|
|
30
30
|
pluginname: string;
|
|
31
31
|
/**
|
|
@@ -33,7 +33,7 @@ export declare class Context {
|
|
|
33
33
|
* @param core Core 实例
|
|
34
34
|
* @param pluginname 插件名称
|
|
35
35
|
*/
|
|
36
|
-
constructor(core: Core, pluginname: string, injections?: Record<string, any>);
|
|
36
|
+
constructor(core: Core, pluginname: string, instance?: any, injections?: Record<string, any>);
|
|
37
37
|
/**
|
|
38
38
|
* 注入依赖
|
|
39
39
|
* @param name 依赖名称
|
|
@@ -101,7 +101,7 @@ export declare class Context {
|
|
|
101
101
|
* @param plugin 插件实例
|
|
102
102
|
* @param config 插件配置
|
|
103
103
|
*/
|
|
104
|
-
apply(plugin: Plugin, config:
|
|
104
|
+
apply(plugin: Plugin, config: any): Promise<void>;
|
|
105
105
|
/**
|
|
106
106
|
* 注册 i18n
|
|
107
107
|
* @param content 内容(可以是嵌套对象或单个key)
|
package/dist/context.js
CHANGED
|
@@ -17,6 +17,7 @@ class Context {
|
|
|
17
17
|
childPlugins = new Map();
|
|
18
18
|
i18ns = [];
|
|
19
19
|
component;
|
|
20
|
+
instance;
|
|
20
21
|
/** 插件名称 */
|
|
21
22
|
pluginname;
|
|
22
23
|
/**
|
|
@@ -24,8 +25,9 @@ class Context {
|
|
|
24
25
|
* @param core Core 实例
|
|
25
26
|
* @param pluginname 插件名称
|
|
26
27
|
*/
|
|
27
|
-
constructor(core, pluginname, injections = {}) {
|
|
28
|
+
constructor(core, pluginname, instance, injections = {}) {
|
|
28
29
|
this.core = core;
|
|
30
|
+
this.instance = instance;
|
|
29
31
|
this.pluginname = pluginname;
|
|
30
32
|
this.childPlugins = new Map();
|
|
31
33
|
this.component = injections;
|
|
@@ -46,10 +48,10 @@ class Context {
|
|
|
46
48
|
route(path) {
|
|
47
49
|
if (this.core.routes[path]) {
|
|
48
50
|
this.core.logger.warn(`Plugin "${this.pluginname}" attempt to register route "${path}", but it has already been registered.`);
|
|
49
|
-
return new route_1.Route(path);
|
|
51
|
+
return new route_1.Route(path, this);
|
|
50
52
|
}
|
|
51
53
|
this.routes.push(path);
|
|
52
|
-
return this.core.route(path);
|
|
54
|
+
return this.core.route(path, this);
|
|
53
55
|
}
|
|
54
56
|
/**
|
|
55
57
|
* 注册事件
|
|
@@ -195,10 +197,7 @@ class Context {
|
|
|
195
197
|
this.middlewares.forEach((middleware) => delete this.core.globalMiddlewares[middleware]);
|
|
196
198
|
// 删除事件监听器
|
|
197
199
|
this.eventlisteners.forEach(({ name, listener }) => {
|
|
198
|
-
|
|
199
|
-
if (listeners) {
|
|
200
|
-
this.core.eventListeners[name] = listeners.filter(l => l !== listener);
|
|
201
|
-
}
|
|
200
|
+
this.core.off(name, listener);
|
|
202
201
|
});
|
|
203
202
|
// 删除钩子
|
|
204
203
|
for (const hook in this.hooks) {
|
package/dist/core.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
1
2
|
import { Config } from './config';
|
|
2
3
|
import { Logger } from './logger';
|
|
3
4
|
import { Session } from './session';
|
|
@@ -7,6 +8,7 @@ import { Context } from './context';
|
|
|
7
8
|
import { HookHandler, Hook } from './hook';
|
|
8
9
|
import { Server as CoreServer } from './server';
|
|
9
10
|
import { I18n } from './i18n';
|
|
11
|
+
import { IRenderer } from '@yumerijs/types';
|
|
10
12
|
interface Plugin {
|
|
11
13
|
apply: (ctx: Context, config: Config) => Promise<void>;
|
|
12
14
|
disable: (ctx: Context) => Promise<void>;
|
|
@@ -27,9 +29,7 @@ export declare const enum PluginStatus {
|
|
|
27
29
|
PENDING = "pending"
|
|
28
30
|
}
|
|
29
31
|
export declare class Core {
|
|
30
|
-
|
|
31
|
-
[event: string]: ((...args: any[]) => Promise<void>)[];
|
|
32
|
-
};
|
|
32
|
+
emitter: EventEmitter<[never]>;
|
|
33
33
|
components: {
|
|
34
34
|
[name: string]: any;
|
|
35
35
|
};
|
|
@@ -41,7 +41,11 @@ export declare class Core {
|
|
|
41
41
|
server: CoreServer;
|
|
42
42
|
i18n: I18n;
|
|
43
43
|
loader: any;
|
|
44
|
+
renderers: Map<string, IRenderer>;
|
|
45
|
+
pluginRenderers: Map<string, string>;
|
|
44
46
|
constructor(loader?: any, coreConfig?: CoreOptions, setCore?: boolean);
|
|
47
|
+
addRenderer(renderer: IRenderer): void;
|
|
48
|
+
getRendererForPlugin(pluginName: string): string | undefined;
|
|
45
49
|
runCore(): Promise<void>;
|
|
46
50
|
getShortPluginName(pluginName: string): string;
|
|
47
51
|
plugin(pluginInstance: Plugin, context: Context, config: Config): Promise<void>;
|
|
@@ -49,9 +53,10 @@ export declare class Core {
|
|
|
49
53
|
getComponent(name: string): any;
|
|
50
54
|
unregisterComponent(name: string): void;
|
|
51
55
|
on(event: string, listener: (...args: any[]) => Promise<void>): void;
|
|
52
|
-
emit(event: string, ...
|
|
56
|
+
emit(event: string, ...payload: any): void;
|
|
57
|
+
off(event: string, listener: (...args: any[]) => Promise<void>): void;
|
|
53
58
|
use(name: string, middleware: Middleware): Core;
|
|
54
|
-
route(path: string): Route;
|
|
59
|
+
route(path: string, context: Context): Route;
|
|
55
60
|
hook(name: string, hookname: string, callback: HookHandler): any;
|
|
56
61
|
unhook(name: string, hookname: string): any;
|
|
57
62
|
hookExecute(name: string, ...args: any[]): Promise<any[]>;
|
package/dist/core.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Core = void 0;
|
|
4
|
+
const events_1 = require("events");
|
|
4
5
|
const logger_1 = require("./logger");
|
|
5
6
|
const route_1 = require("./route");
|
|
6
7
|
const hook_1 = require("./hook");
|
|
7
8
|
const server_1 = require("./server");
|
|
8
9
|
class Core {
|
|
9
|
-
|
|
10
|
+
emitter = new events_1.EventEmitter();
|
|
10
11
|
components = {};
|
|
11
12
|
routes = {};
|
|
12
13
|
logger = new logger_1.Logger('core');
|
|
@@ -16,12 +17,23 @@ class Core {
|
|
|
16
17
|
server;
|
|
17
18
|
i18n;
|
|
18
19
|
loader;
|
|
20
|
+
renderers = new Map();
|
|
21
|
+
pluginRenderers = new Map(); // Stores which plugin uses which renderer
|
|
19
22
|
constructor(loader, coreConfig, setCore = true) {
|
|
20
23
|
this.coreConfig = coreConfig || {};
|
|
21
24
|
this.loader = loader;
|
|
22
25
|
if (setCore)
|
|
23
26
|
logger_1.Logger.setCore(this);
|
|
24
27
|
}
|
|
28
|
+
addRenderer(renderer) {
|
|
29
|
+
if (this.renderers.has(renderer.name)) {
|
|
30
|
+
this.logger.warn(`Renderer "${renderer.name}" is already registered and will be overwritten.`);
|
|
31
|
+
}
|
|
32
|
+
this.renderers.set(renderer.name, renderer);
|
|
33
|
+
}
|
|
34
|
+
getRendererForPlugin(pluginName) {
|
|
35
|
+
return this.pluginRenderers.get(pluginName);
|
|
36
|
+
}
|
|
25
37
|
async runCore() {
|
|
26
38
|
this.server = new server_1.Server(this, {
|
|
27
39
|
port: this.coreConfig.port || 14510,
|
|
@@ -44,6 +56,7 @@ class Core {
|
|
|
44
56
|
}
|
|
45
57
|
async plugin(pluginInstance, context, config) {
|
|
46
58
|
const shortName = this.getShortPluginName(context.pluginname);
|
|
59
|
+
context.instance = pluginInstance;
|
|
47
60
|
this.logger.info(`apply plugin ${shortName}`);
|
|
48
61
|
if (pluginInstance.apply) {
|
|
49
62
|
await pluginInstance.apply(context, config);
|
|
@@ -59,29 +72,28 @@ class Core {
|
|
|
59
72
|
delete this.components[name];
|
|
60
73
|
}
|
|
61
74
|
on(event, listener) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
this.emitter.on(event, (...args) => {
|
|
76
|
+
// 包一层保证 async 可以被捕获
|
|
77
|
+
Promise.resolve(listener(...args)).catch((err) => {
|
|
78
|
+
console.error(`Error in event listener for "${event}":`, err);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
66
81
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
82
|
+
emit(event, ...payload) {
|
|
83
|
+
this.emitter.emit(event, ...payload);
|
|
84
|
+
}
|
|
85
|
+
// 删除监听器
|
|
86
|
+
off(event, listener) {
|
|
87
|
+
// 原生 EventEmitter 必须删“同一个函数引用”
|
|
88
|
+
// 所以必须包装一致,这里我们直接用 listener 本体删
|
|
89
|
+
this.emitter.off(event, listener);
|
|
78
90
|
}
|
|
79
91
|
use(name, middleware) {
|
|
80
92
|
this.globalMiddlewares[name] = middleware;
|
|
81
93
|
return this;
|
|
82
94
|
}
|
|
83
|
-
route(path) {
|
|
84
|
-
const route = new route_1.Route(path);
|
|
95
|
+
route(path, context) {
|
|
96
|
+
const route = new route_1.Route(path, context);
|
|
85
97
|
this.routes[path] = route;
|
|
86
98
|
return route;
|
|
87
99
|
}
|
|
@@ -108,18 +120,62 @@ class Core {
|
|
|
108
120
|
const result = route.match(pathname);
|
|
109
121
|
if (result) {
|
|
110
122
|
try {
|
|
123
|
+
this.emit('request:start', {
|
|
124
|
+
path: pathname,
|
|
125
|
+
route: routePath,
|
|
126
|
+
method: session?.client?.req?.method,
|
|
127
|
+
plugin: route.context?.pluginname,
|
|
128
|
+
start: Date.now(),
|
|
129
|
+
sessionId: session?.sessionid,
|
|
130
|
+
});
|
|
111
131
|
const middlewares = [...Object.values(this.globalMiddlewares), ...(route.middlewares || [])];
|
|
112
132
|
let index = 0;
|
|
113
133
|
const runner = async () => {
|
|
114
|
-
if (index < middlewares.length)
|
|
134
|
+
if (index < middlewares.length) {
|
|
135
|
+
const name = Object.keys(this.globalMiddlewares)[index] || `mw-${index}`;
|
|
136
|
+
const start = Date.now();
|
|
115
137
|
await middlewares[index++](session, runner);
|
|
116
|
-
|
|
138
|
+
this.emit('middleware:end', {
|
|
139
|
+
name,
|
|
140
|
+
path: pathname,
|
|
141
|
+
plugin: route.context?.pluginname,
|
|
142
|
+
duration: Date.now() - start,
|
|
143
|
+
sessionId: session?.sessionid,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
const start = Date.now();
|
|
117
148
|
await route.executeHandler(session, queryParams, result.pathParams);
|
|
149
|
+
this.emit('route:end', {
|
|
150
|
+
path: pathname,
|
|
151
|
+
route: routePath,
|
|
152
|
+
plugin: route.context?.pluginname,
|
|
153
|
+
duration: Date.now() - start,
|
|
154
|
+
sessionId: session?.sessionid,
|
|
155
|
+
status: session?.status,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
118
158
|
};
|
|
119
159
|
await runner();
|
|
160
|
+
this.emit('request:end', {
|
|
161
|
+
path: pathname,
|
|
162
|
+
route: routePath,
|
|
163
|
+
method: session?.client?.req?.method,
|
|
164
|
+
plugin: route.context?.pluginname,
|
|
165
|
+
duration: Date.now() - session._startAt || undefined,
|
|
166
|
+
status: session?.status,
|
|
167
|
+
sessionId: session?.sessionid,
|
|
168
|
+
});
|
|
120
169
|
}
|
|
121
170
|
catch (error) {
|
|
122
171
|
this.logger.error(`Unhandled error in route execution for path "${pathname}":`, error);
|
|
172
|
+
this.emit('request:error', {
|
|
173
|
+
path: pathname,
|
|
174
|
+
route: routePath,
|
|
175
|
+
plugin: route.context?.pluginname,
|
|
176
|
+
error: String(error),
|
|
177
|
+
sessionId: session?.sessionid,
|
|
178
|
+
});
|
|
123
179
|
if (session && session.client.res && !session.client.res.writableEnded) {
|
|
124
180
|
session.client.res.statusCode = 500;
|
|
125
181
|
session.client.res.end('Internal Server Error');
|
package/dist/i18n.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare class I18n {
|
|
|
23
23
|
* 替换模板字符串中的文本点
|
|
24
24
|
* e.g. "Hello {{app.title}}" -> "Hello 世界"
|
|
25
25
|
*/
|
|
26
|
-
replaceAll(input: string, langs?: string[]): string;
|
|
26
|
+
replaceAll(input: string, langs?: string[], customRegex?: RegExp): string;
|
|
27
27
|
all(): I18nData;
|
|
28
28
|
}
|
|
29
29
|
export {};
|
package/dist/i18n.js
CHANGED
|
@@ -69,8 +69,9 @@ class I18n {
|
|
|
69
69
|
* 替换模板字符串中的文本点
|
|
70
70
|
* e.g. "Hello {{app.title}}" -> "Hello 世界"
|
|
71
71
|
*/
|
|
72
|
-
replaceAll(input, langs) {
|
|
73
|
-
|
|
72
|
+
replaceAll(input, langs, customRegex) {
|
|
73
|
+
const regex = customRegex || /\{\{\s*([\w.]+)\s*\}\}/g;
|
|
74
|
+
return input.replace(regex, (_, key) => this.get(key.trim(), langs));
|
|
74
75
|
}
|
|
75
76
|
all() {
|
|
76
77
|
return this.data;
|
package/dist/route.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { Session } from './session';
|
|
7
7
|
import { Middleware } from './middleware';
|
|
8
8
|
import { WebSocketServer } from 'ws';
|
|
9
|
+
import { Context } from './context';
|
|
9
10
|
export type RouteHandler = (session: Session, queryParams: URLSearchParams, ...pathParams: string[]) => Promise<void> | void;
|
|
10
11
|
/**
|
|
11
12
|
* Route class using segment-based matching (no fragile capture-group-index mapping).
|
|
@@ -23,11 +24,13 @@ export declare class Route {
|
|
|
23
24
|
middlewares: Middleware[];
|
|
24
25
|
allowedMethods: string[];
|
|
25
26
|
ws: WebSocketServer;
|
|
27
|
+
context: Context;
|
|
26
28
|
/**
|
|
27
29
|
* 创建路由
|
|
28
30
|
* @param path 路由路径
|
|
31
|
+
* @param context 插件上下文
|
|
29
32
|
*/
|
|
30
|
-
constructor(path: string);
|
|
33
|
+
constructor(path: string, context: Context);
|
|
31
34
|
/**
|
|
32
35
|
* 设置路由处理器
|
|
33
36
|
* @param handler 路由处理器
|
package/dist/route.js
CHANGED
|
@@ -41,12 +41,15 @@ class Route {
|
|
|
41
41
|
middlewares = [];
|
|
42
42
|
allowedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'];
|
|
43
43
|
ws = null;
|
|
44
|
+
context;
|
|
44
45
|
/**
|
|
45
46
|
* 创建路由
|
|
46
47
|
* @param path 路由路径
|
|
48
|
+
* @param context 插件上下文
|
|
47
49
|
*/
|
|
48
|
-
constructor(path) {
|
|
50
|
+
constructor(path, context) {
|
|
49
51
|
this.path = path;
|
|
52
|
+
this.context = context;
|
|
50
53
|
const { segments, params } = parsePatternToSegments(path);
|
|
51
54
|
this.segments = segments;
|
|
52
55
|
this.paramsInfo = params;
|
package/dist/server.d.ts
CHANGED
package/dist/server.js
CHANGED
|
@@ -44,6 +44,7 @@ const fs = __importStar(require("fs"));
|
|
|
44
44
|
const path = __importStar(require("path"));
|
|
45
45
|
const url_1 = require("url");
|
|
46
46
|
const mime = __importStar(require("mime-types"));
|
|
47
|
+
const types_1 = require("@yumerijs/types");
|
|
47
48
|
const logger = new logger_1.Logger('server');
|
|
48
49
|
function isStream(value) {
|
|
49
50
|
return value && typeof value.pipe === "function";
|
|
@@ -62,8 +63,11 @@ class Server {
|
|
|
62
63
|
this.enableCors = config.enableCors ?? true;
|
|
63
64
|
this.staticDir = config.staticDir ?? 'static';
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
getStaticDir() {
|
|
67
|
+
return this.staticDir;
|
|
68
|
+
}
|
|
69
|
+
createSession(ip, cookies, res, req, pathname, pluginContext, extra = {}) {
|
|
70
|
+
const session = new session_1.Session(ip, cookies, this, req, res, pathname, undefined, pluginContext);
|
|
67
71
|
Object.assign(session.properties, extra);
|
|
68
72
|
session.protocol = extra.protocol ?? 'http';
|
|
69
73
|
return session;
|
|
@@ -112,9 +116,21 @@ class Server {
|
|
|
112
116
|
const queryParams = url.searchParams;
|
|
113
117
|
const ip = this.getClientIP(req);
|
|
114
118
|
const cookies = this.parseCookies(req);
|
|
115
|
-
|
|
119
|
+
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
120
|
+
const virtualAsset = await (0, types_1.resolveVirtualAsset)(pathname);
|
|
121
|
+
if (virtualAsset) {
|
|
122
|
+
const headers = virtualAsset.headers || {};
|
|
123
|
+
headers['Content-Type'] = headers['Content-Type'] || virtualAsset.contentType || 'application/octet-stream';
|
|
124
|
+
res.writeHead(200, headers);
|
|
125
|
+
res.end(virtualAsset.body);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
116
129
|
const route = this.core.getRoute(pathname);
|
|
117
130
|
const rootroute = this.core.getRoute('root');
|
|
131
|
+
const pluginContext = route ? route.context : (rootroute ? rootroute.context : undefined);
|
|
132
|
+
const session = this.createSession(ip, cookies, res, req, pathname, pluginContext, { protocol: 'http', header: req.headers });
|
|
133
|
+
session._startAt = Date.now();
|
|
118
134
|
const handleRoute = async (routePath) => {
|
|
119
135
|
const matched = await this.core.executeRoute(routePath, session, queryParams);
|
|
120
136
|
if (!matched) {
|
|
@@ -180,8 +196,9 @@ class Server {
|
|
|
180
196
|
const queryParams = url.searchParams;
|
|
181
197
|
const ip = this.getClientIP(req);
|
|
182
198
|
const cookies = this.parseCookies(req);
|
|
183
|
-
const session = this.createSession(ip, cookies, null, req, pathname, { protocol: 'http', header: req.headers });
|
|
184
199
|
const route = this.core.getRoute(pathname);
|
|
200
|
+
const pluginContext = route ? route.context : undefined;
|
|
201
|
+
const session = this.createSession(ip, cookies, null, req, pathname, pluginContext, { protocol: 'http', header: req.headers });
|
|
185
202
|
if (route && route.ws != null) {
|
|
186
203
|
const matched = await this.core.executeRoute(pathname, session, queryParams);
|
|
187
204
|
if (!matched) {
|
package/dist/session.d.ts
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
import { Server } from './server';
|
|
7
7
|
import { IncomingMessage, ServerResponse } from 'http';
|
|
8
8
|
import { Stream } from "stream";
|
|
9
|
+
import { Context } from './context';
|
|
9
10
|
type ParsedParams = Record<string, string | string[] | undefined>;
|
|
11
|
+
type MissingMode = 'keep-template' | 'keep-key' | 'remove';
|
|
10
12
|
export interface Client {
|
|
11
13
|
req: IncomingMessage;
|
|
12
14
|
res: ServerResponse;
|
|
@@ -63,13 +65,14 @@ export declare class Session {
|
|
|
63
65
|
pathname: string;
|
|
64
66
|
languages: string[];
|
|
65
67
|
responseHandled: boolean;
|
|
68
|
+
pluginContext: Context | undefined;
|
|
66
69
|
/**
|
|
67
70
|
* @constructor
|
|
68
71
|
* @param ip 用户IP
|
|
69
72
|
* @param cookie 会话cookie
|
|
70
73
|
* @param query 请求字符串
|
|
71
74
|
*/
|
|
72
|
-
constructor(ip: string, cookie: Record<string, string>, server: Server, req?: IncomingMessage, res?: ServerResponse, pathname?: string, query?: Record<string, string
|
|
75
|
+
constructor(ip: string, cookie: Record<string, string>, server: Server, req?: IncomingMessage, res?: ServerResponse, pathname?: string, query?: Record<string, string>, pluginContext?: Context);
|
|
73
76
|
response<T extends ResType>(body: BodyMap[T], type?: T): void;
|
|
74
77
|
get restype(): ResType;
|
|
75
78
|
get body(): string;
|
|
@@ -136,5 +139,19 @@ export declare class Session {
|
|
|
136
139
|
* @param isStream 是否流式传输
|
|
137
140
|
*/
|
|
138
141
|
sendFile(path: string, isStream?: boolean): void;
|
|
142
|
+
/**
|
|
143
|
+
* 渲染模板
|
|
144
|
+
* @template 模板字符串
|
|
145
|
+
* @data 数据
|
|
146
|
+
* @missing 缺失值处理方式,keep-template: 保留模板,keep-key: 保留键,remove: 移除
|
|
147
|
+
* @regex 模板匹配正则,默认为{{ xxx.xxx }}
|
|
148
|
+
*/
|
|
149
|
+
render(template: string, data: Record<string, any>, missing?: MissingMode, regex?: RegExp): string;
|
|
150
|
+
/**
|
|
151
|
+
* Renders a UI component using the plugin's declared renderer.
|
|
152
|
+
* @param component The component object to render.
|
|
153
|
+
* @param data The data/props to pass to the component.
|
|
154
|
+
*/
|
|
155
|
+
renderView(component: any, data?: Record<string, any>): Promise<void>;
|
|
139
156
|
}
|
|
140
157
|
export {};
|
package/dist/session.js
CHANGED
|
@@ -63,18 +63,20 @@ class Session {
|
|
|
63
63
|
pathname;
|
|
64
64
|
languages;
|
|
65
65
|
responseHandled = false;
|
|
66
|
+
pluginContext;
|
|
66
67
|
/**
|
|
67
68
|
* @constructor
|
|
68
69
|
* @param ip 用户IP
|
|
69
70
|
* @param cookie 会话cookie
|
|
70
71
|
* @param query 请求字符串
|
|
71
72
|
*/
|
|
72
|
-
constructor(ip, cookie, server, req, res, pathname, query) {
|
|
73
|
+
constructor(ip, cookie, server, req, res, pathname, query, pluginContext) {
|
|
73
74
|
this.ip = ip;
|
|
74
75
|
this.cookie = cookie;
|
|
75
76
|
this.query = query;
|
|
76
77
|
this.server = server;
|
|
77
78
|
this.pathname = pathname;
|
|
79
|
+
this.pluginContext = pluginContext;
|
|
78
80
|
let header = {};
|
|
79
81
|
for (let key in req.headers) {
|
|
80
82
|
const value = req.headers[key];
|
|
@@ -395,5 +397,64 @@ class Session {
|
|
|
395
397
|
this.response(fs_1.default.readFileSync(path), 'buffer');
|
|
396
398
|
}
|
|
397
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* 渲染模板
|
|
402
|
+
* @template 模板字符串
|
|
403
|
+
* @data 数据
|
|
404
|
+
* @missing 缺失值处理方式,keep-template: 保留模板,keep-key: 保留键,remove: 移除
|
|
405
|
+
* @regex 模板匹配正则,默认为{{ xxx.xxx }}
|
|
406
|
+
*/
|
|
407
|
+
render(template, data, missing = 'keep-template', regex = /\{\{\s*([\w.]+)\s*\}\}/g) {
|
|
408
|
+
const getValue = (obj, path) => {
|
|
409
|
+
return path.split('.').reduce((acc, key) => acc?.[key], obj);
|
|
410
|
+
};
|
|
411
|
+
return template.replace(regex, (_, key) => {
|
|
412
|
+
const value = getValue(data, key);
|
|
413
|
+
if (value !== undefined)
|
|
414
|
+
return String(value);
|
|
415
|
+
switch (missing) {
|
|
416
|
+
case 'keep-key':
|
|
417
|
+
return key;
|
|
418
|
+
case 'remove':
|
|
419
|
+
return '';
|
|
420
|
+
case 'keep-template':
|
|
421
|
+
default:
|
|
422
|
+
return _;
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Renders a UI component using the plugin's declared renderer.
|
|
428
|
+
* @param component The component object to render.
|
|
429
|
+
* @param data The data/props to pass to the component.
|
|
430
|
+
*/
|
|
431
|
+
async renderView(component, data = {}) {
|
|
432
|
+
if (!this.pluginContext) {
|
|
433
|
+
throw new Error(`Cannot call 'renderView' because the session is not associated with a plugin context.`);
|
|
434
|
+
}
|
|
435
|
+
const rendererName = this.pluginContext.instance.render;
|
|
436
|
+
const pluginName = this.pluginContext.pluginname;
|
|
437
|
+
if (!rendererName) {
|
|
438
|
+
this.server.core.logger.error(`Plugin "${pluginName}" uses 'renderView' but did not declare a renderer. Please add 'export const render = "your-renderer-name";' to your plugin's entry file.`);
|
|
439
|
+
// throw new Error(`Plugin "${pluginName}" did not declare a renderer.`);
|
|
440
|
+
}
|
|
441
|
+
const renderer = this.server.core.renderers.get(rendererName);
|
|
442
|
+
if (!renderer) {
|
|
443
|
+
this.server.core.logger.error(`Renderer "${rendererName}" declared by plugin "${pluginName}" is not registered. Have you installed the renderer package (e.g., '@yumerijs/vue-renderer')?`);
|
|
444
|
+
// throw new Error(`Renderer "${rendererName}" is not registered.`);
|
|
445
|
+
}
|
|
446
|
+
const renderOptions = {
|
|
447
|
+
pluginName,
|
|
448
|
+
};
|
|
449
|
+
try {
|
|
450
|
+
const html = await renderer.render(component, data, renderOptions);
|
|
451
|
+
this.setMime('text/html');
|
|
452
|
+
this.response(html, 'plain');
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
this.server.core.logger.error(`Error while rendering view for plugin "${pluginName}":`, error);
|
|
456
|
+
throw error;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
398
459
|
}
|
|
399
460
|
exports.Session = Session;
|