@yumerijs/core 1.3.3 → 2.0.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/context.d.ts +31 -17
- package/dist/context.js +98 -28
- package/dist/core.d.ts +9 -158
- package/dist/core.js +20 -483
- package/dist/hook.js +2 -2
- package/dist/i18n.d.ts +29 -0
- package/dist/i18n.js +79 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/logger.d.ts +6 -2
- package/dist/logger.js +15 -8
- package/dist/route.d.ts +7 -0
- package/dist/route.js +122 -104
- package/dist/server.d.ts +1 -4
- package/dist/server.js +48 -92
- package/dist/session.d.ts +45 -3
- package/dist/session.js +167 -15
- package/package.json +1 -2
package/dist/i18n.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @time: 2025/10/28 22:13
|
|
4
|
+
* @author: FireGuo
|
|
5
|
+
* WindyPear-Team All right reserved
|
|
6
|
+
**/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.I18n = void 0;
|
|
9
|
+
class I18n {
|
|
10
|
+
data = {};
|
|
11
|
+
fallback;
|
|
12
|
+
constructor(fallback = ['en']) {
|
|
13
|
+
this.fallback = fallback;
|
|
14
|
+
}
|
|
15
|
+
register(key, lang) {
|
|
16
|
+
if (typeof key === 'string' && lang) {
|
|
17
|
+
if (!this.data[key])
|
|
18
|
+
this.data[key] = {};
|
|
19
|
+
Object.assign(this.data[key], lang);
|
|
20
|
+
}
|
|
21
|
+
else if (typeof key === 'object') {
|
|
22
|
+
this.flattenAndRegister(key);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
setFallback(fallback) {
|
|
26
|
+
this.fallback = fallback;
|
|
27
|
+
}
|
|
28
|
+
flattenAndRegister(obj, prefix = '') {
|
|
29
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
30
|
+
const fullKey = prefix ? `${prefix}.${k}` : k;
|
|
31
|
+
if (typeof v === 'object' && !('zh' in v || 'en' in v)) {
|
|
32
|
+
this.flattenAndRegister(v, fullKey);
|
|
33
|
+
}
|
|
34
|
+
else if (typeof v === 'object') {
|
|
35
|
+
if (!this.data[fullKey])
|
|
36
|
+
this.data[fullKey] = {};
|
|
37
|
+
Object.assign(this.data[fullKey], v);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
isRegistered(key) {
|
|
42
|
+
return !!this.data[key];
|
|
43
|
+
}
|
|
44
|
+
delete(key) {
|
|
45
|
+
delete this.data[key];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 获取指定key的翻译
|
|
49
|
+
* @param key 文本点
|
|
50
|
+
* @param langs 用户的语言优先级数组
|
|
51
|
+
*/
|
|
52
|
+
get(key, langs) {
|
|
53
|
+
const entry = this.data[key];
|
|
54
|
+
if (!entry)
|
|
55
|
+
return key;
|
|
56
|
+
if (langs && langs.length) {
|
|
57
|
+
for (const l of langs) {
|
|
58
|
+
if (entry[l])
|
|
59
|
+
return entry[l];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const fb of this.fallback) {
|
|
63
|
+
if (entry[fb])
|
|
64
|
+
return entry[fb];
|
|
65
|
+
}
|
|
66
|
+
return key;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 替换模板字符串中的文本点
|
|
70
|
+
* e.g. "Hello {{app.title}}" -> "Hello 世界"
|
|
71
|
+
*/
|
|
72
|
+
replaceAll(input, langs) {
|
|
73
|
+
return input.replace(/\{\{(.*?)\}\}/g, (_, key) => this.get(key.trim(), langs));
|
|
74
|
+
}
|
|
75
|
+
all() {
|
|
76
|
+
return this.data;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.I18n = I18n;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/logger.d.ts
CHANGED
|
@@ -8,10 +8,14 @@ export declare class Logger {
|
|
|
8
8
|
private title;
|
|
9
9
|
private titleColor;
|
|
10
10
|
static coreInstance: Core | null;
|
|
11
|
-
static logs:
|
|
11
|
+
static logs: {
|
|
12
|
+
level: string;
|
|
13
|
+
message: string;
|
|
14
|
+
timestamp: string;
|
|
15
|
+
}[];
|
|
12
16
|
static setCore(core: Core): void;
|
|
13
17
|
constructor(title: string);
|
|
14
|
-
private
|
|
18
|
+
private getColorByName;
|
|
15
19
|
private getTimestamp;
|
|
16
20
|
private log;
|
|
17
21
|
info(...args: any[]): void;
|
package/dist/logger.js
CHANGED
|
@@ -17,22 +17,28 @@ class Logger {
|
|
|
17
17
|
static logs = [];
|
|
18
18
|
static setCore(core) {
|
|
19
19
|
if (Logger.coreInstance !== null) {
|
|
20
|
-
|
|
21
|
-
logger.warn("Logger.coreInstance is already set. Overwriting would cause confusion.");
|
|
20
|
+
return;
|
|
22
21
|
}
|
|
23
22
|
Logger.coreInstance = core;
|
|
24
23
|
}
|
|
25
24
|
constructor(title) {
|
|
26
25
|
this.title = title;
|
|
27
|
-
this.titleColor = this.
|
|
26
|
+
this.titleColor = this.getColorByName(this.title);
|
|
28
27
|
}
|
|
29
|
-
|
|
28
|
+
getColorByName(name) {
|
|
30
29
|
const availableColors = [
|
|
31
30
|
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,
|
|
31
|
+
picocolors_1.default.magenta, picocolors_1.default.cyan, picocolors_1.default.white,
|
|
32
|
+
picocolors_1.default.redBright, picocolors_1.default.greenBright, picocolors_1.default.yellowBright,
|
|
33
|
+
picocolors_1.default.blueBright, picocolors_1.default.magentaBright, picocolors_1.default.cyanBright
|
|
33
34
|
];
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
// Simple hash function to ensure consistent color for the same title
|
|
36
|
+
let hash = 0;
|
|
37
|
+
for (let i = 0; i < name.length; i++) {
|
|
38
|
+
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
|
39
|
+
}
|
|
40
|
+
const index = Math.abs(hash % availableColors.length);
|
|
41
|
+
return availableColors[index];
|
|
36
42
|
}
|
|
37
43
|
getTimestamp() {
|
|
38
44
|
const now = new Date();
|
|
@@ -43,7 +49,8 @@ class Logger {
|
|
|
43
49
|
const levelColor = level === 'E' ? picocolors_1.default.red : level === 'W' ? picocolors_1.default.yellow : picocolors_1.default.cyan;
|
|
44
50
|
const timestamp = this.getTimestamp();
|
|
45
51
|
console.log(`${picocolors_1.default.gray(timestamp)} [${levelColor(level)}] ${this.titleColor(this.title)}`, ...args);
|
|
46
|
-
Logger.coreInstance?.emit('log', { level, message: args.join(' ') });
|
|
52
|
+
Logger.coreInstance?.emit('log', { level, message: args.join(' '), timestamp });
|
|
53
|
+
Logger.logs.push({ level, message: args.join(' '), timestamp });
|
|
47
54
|
}
|
|
48
55
|
info(...args) { this.log('I', ...args); }
|
|
49
56
|
warn(...args) { this.log('W', ...args); }
|
package/dist/route.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
**/
|
|
6
6
|
import { Session } from './session';
|
|
7
7
|
import { Middleware } from './middleware';
|
|
8
|
+
import { WebSocketServer } from 'ws';
|
|
8
9
|
export type RouteHandler = (session: Session, queryParams: URLSearchParams, ...pathParams: string[]) => Promise<void> | void;
|
|
9
10
|
/**
|
|
10
11
|
* Route class using segment-based matching (no fragile capture-group-index mapping).
|
|
@@ -21,6 +22,7 @@ export declare class Route {
|
|
|
21
22
|
private handler;
|
|
22
23
|
middlewares: Middleware[];
|
|
23
24
|
allowedMethods: string[];
|
|
25
|
+
ws: WebSocketServer;
|
|
24
26
|
/**
|
|
25
27
|
* 创建路由
|
|
26
28
|
* @param path 路由路径
|
|
@@ -60,4 +62,9 @@ export declare class Route {
|
|
|
60
62
|
* @returns this
|
|
61
63
|
*/
|
|
62
64
|
methods(...methods: string[]): Route;
|
|
65
|
+
/**
|
|
66
|
+
* 注册Ws处理器
|
|
67
|
+
* @param handler 处理器
|
|
68
|
+
*/
|
|
69
|
+
wsOn(event: string, handler: (...args: any[]) => void): this;
|
|
63
70
|
}
|
package/dist/route.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Route = void 0;
|
|
4
|
+
const ws_1 = require("ws");
|
|
4
5
|
function parsePatternToSegments(pattern) {
|
|
5
6
|
// Normalize: remove leading/trailing slashes for consistent splitting
|
|
6
7
|
const norm = pattern.replace(/^\/+|\/+$/g, '');
|
|
@@ -39,6 +40,7 @@ class Route {
|
|
|
39
40
|
handler = null;
|
|
40
41
|
middlewares = [];
|
|
41
42
|
allowedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'];
|
|
43
|
+
ws = null;
|
|
42
44
|
/**
|
|
43
45
|
* 创建路由
|
|
44
46
|
* @param path 路由路径
|
|
@@ -73,129 +75,135 @@ class Route {
|
|
|
73
75
|
* @returns 匹配结果
|
|
74
76
|
*/
|
|
75
77
|
match(pathname) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
return null;
|
|
89
|
-
if (parts[j] !== seg.value)
|
|
90
|
-
return null;
|
|
91
|
-
i++;
|
|
92
|
-
j++;
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
// seg.type === 'param'
|
|
96
|
-
const nextSegIsStatic = (() => {
|
|
97
|
-
const next = this.segments[i + 1];
|
|
98
|
-
return !!next && next.type === 'static';
|
|
99
|
-
})();
|
|
100
|
-
switch (seg.modifier) {
|
|
101
|
-
case '': { // required single segment
|
|
78
|
+
if (pathname.startsWith('/')) {
|
|
79
|
+
// normalize pathname: remove leading/trailing slashes
|
|
80
|
+
const normPath = pathname.replace(/^\/+|\/+$/g, '');
|
|
81
|
+
const parts = normPath === '' ? [] : normPath.split('/');
|
|
82
|
+
const params = {};
|
|
83
|
+
const pathParams = [];
|
|
84
|
+
let i = 0; // index into segments (pattern)
|
|
85
|
+
let j = 0; // index into parts (path)
|
|
86
|
+
while (i < this.segments.length) {
|
|
87
|
+
const seg = this.segments[i];
|
|
88
|
+
// static must match exactly
|
|
89
|
+
if (seg.type === 'static') {
|
|
102
90
|
if (j >= parts.length)
|
|
103
91
|
return null;
|
|
104
|
-
|
|
105
|
-
|
|
92
|
+
if (parts[j] !== seg.value)
|
|
93
|
+
return null;
|
|
106
94
|
i++;
|
|
107
95
|
j++;
|
|
108
|
-
|
|
96
|
+
continue;
|
|
109
97
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
i++;
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
// otherwise consume one segment as the optional param
|
|
98
|
+
// seg.type === 'param'
|
|
99
|
+
const nextSegIsStatic = (() => {
|
|
100
|
+
const next = this.segments[i + 1];
|
|
101
|
+
return !!next && next.type === 'static';
|
|
102
|
+
})();
|
|
103
|
+
switch (seg.modifier) {
|
|
104
|
+
case '': { // required single segment
|
|
105
|
+
if (j >= parts.length)
|
|
106
|
+
return null;
|
|
124
107
|
params[seg.name] = parts[j];
|
|
125
108
|
pathParams.push(parts[j]);
|
|
109
|
+
i++;
|
|
126
110
|
j++;
|
|
111
|
+
break;
|
|
127
112
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (nextSegIsStatic) {
|
|
141
|
-
const nextStatic = this.segments[i + 1].value;
|
|
142
|
-
let found = -1;
|
|
143
|
-
for (let k = j; k < parts.length; k++) {
|
|
144
|
-
if (parts[k] === nextStatic) {
|
|
145
|
-
found = k;
|
|
146
|
-
break;
|
|
113
|
+
case '?': { // optional single segment
|
|
114
|
+
// If next segment is static, we should not consume the part when it equals that static
|
|
115
|
+
if (j < parts.length) {
|
|
116
|
+
if (nextSegIsStatic) {
|
|
117
|
+
// peek: if current part equals next static, then this optional param is absent
|
|
118
|
+
const nextStatic = this.segments[i + 1].value;
|
|
119
|
+
if (parts[j] === nextStatic) {
|
|
120
|
+
params[seg.name] = undefined;
|
|
121
|
+
pathParams.push('');
|
|
122
|
+
i++;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
147
125
|
}
|
|
126
|
+
// otherwise consume one segment as the optional param
|
|
127
|
+
params[seg.name] = parts[j];
|
|
128
|
+
pathParams.push(parts[j]);
|
|
129
|
+
j++;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
params[seg.name] = undefined;
|
|
133
|
+
pathParams.push('');
|
|
148
134
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
end = found;
|
|
152
|
-
if (end === j)
|
|
153
|
-
return null; // need at least one segment
|
|
135
|
+
i++;
|
|
136
|
+
break;
|
|
154
137
|
}
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
let found = -1;
|
|
169
|
-
for (let k = j; k < parts.length; k++) {
|
|
170
|
-
if (parts[k] === nextStatic) {
|
|
171
|
-
found = k;
|
|
172
|
-
break;
|
|
138
|
+
case '+': { // required multi (one or more segments)
|
|
139
|
+
if (j >= parts.length)
|
|
140
|
+
return null; // must have at least one
|
|
141
|
+
// find boundary where next static occurs (if any)
|
|
142
|
+
let end = parts.length;
|
|
143
|
+
if (nextSegIsStatic) {
|
|
144
|
+
const nextStatic = this.segments[i + 1].value;
|
|
145
|
+
let found = -1;
|
|
146
|
+
for (let k = j; k < parts.length; k++) {
|
|
147
|
+
if (parts[k] === nextStatic) {
|
|
148
|
+
found = k;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
173
151
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
// next static not found => we cannot match the remainder to the next pattern,
|
|
177
|
-
// but '*' can consume all remainder (and then next static will fail later)
|
|
178
|
-
end = parts.length;
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
152
|
+
if (found === -1)
|
|
153
|
+
return null; // next static not found -> cannot satisfy pattern
|
|
181
154
|
end = found;
|
|
155
|
+
if (end === j)
|
|
156
|
+
return null; // need at least one segment
|
|
182
157
|
}
|
|
158
|
+
// consume parts[j..end-1]
|
|
159
|
+
const value = parts.slice(j, end).join('/');
|
|
160
|
+
params[seg.name] = value;
|
|
161
|
+
pathParams.push(value);
|
|
162
|
+
j = end;
|
|
163
|
+
i++;
|
|
164
|
+
break;
|
|
183
165
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
166
|
+
case '*': { // optional multi (zero or more)
|
|
167
|
+
// If next static exists, consume until that static; else consume all remainder (may be zero)
|
|
168
|
+
let end = parts.length;
|
|
169
|
+
if (nextSegIsStatic) {
|
|
170
|
+
const nextStatic = this.segments[i + 1].value;
|
|
171
|
+
let found = -1;
|
|
172
|
+
for (let k = j; k < parts.length; k++) {
|
|
173
|
+
if (parts[k] === nextStatic) {
|
|
174
|
+
found = k;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (found === -1) {
|
|
179
|
+
// next static not found => we cannot match the remainder to the next pattern,
|
|
180
|
+
// but '*' can consume all remainder (and then next static will fail later)
|
|
181
|
+
end = parts.length;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
end = found;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const value = parts.slice(j, end).join('/');
|
|
188
|
+
params[seg.name] = value === '' ? undefined : value;
|
|
189
|
+
pathParams.push(value === '' ? '' : value);
|
|
190
|
+
j = end;
|
|
191
|
+
i++;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
default:
|
|
195
|
+
return null;
|
|
190
196
|
}
|
|
191
|
-
default:
|
|
192
|
-
return null;
|
|
193
197
|
}
|
|
198
|
+
// after finishing pattern, path must be fully consumed (no extra segments)
|
|
199
|
+
if (j !== parts.length)
|
|
200
|
+
return null;
|
|
201
|
+
return { params, pathParams };
|
|
194
202
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return
|
|
203
|
+
else if (pathname.startsWith('root') && this.path == pathname) {
|
|
204
|
+
return { params: { pathname }, pathParams: [pathname] };
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
199
207
|
}
|
|
200
208
|
/**
|
|
201
209
|
* 执行路由处理器
|
|
@@ -217,5 +225,15 @@ class Route {
|
|
|
217
225
|
this.allowedMethods = methods;
|
|
218
226
|
return this;
|
|
219
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* 注册Ws处理器
|
|
230
|
+
* @param handler 处理器
|
|
231
|
+
*/
|
|
232
|
+
wsOn(event, handler) {
|
|
233
|
+
if (!this.ws)
|
|
234
|
+
this.ws = new ws_1.WebSocketServer({ noServer: true });
|
|
235
|
+
this.ws.on(event, handler);
|
|
236
|
+
return this;
|
|
237
|
+
}
|
|
220
238
|
}
|
|
221
239
|
exports.Route = Route;
|
package/dist/server.d.ts
CHANGED
|
@@ -7,19 +7,16 @@ export interface ServerConfig {
|
|
|
7
7
|
enableWs: boolean;
|
|
8
8
|
}
|
|
9
9
|
export declare class Server {
|
|
10
|
-
|
|
10
|
+
core: Core;
|
|
11
11
|
private port;
|
|
12
12
|
private host;
|
|
13
13
|
private enableCors;
|
|
14
14
|
private staticDir;
|
|
15
|
-
private enableWs;
|
|
16
15
|
private httpServer;
|
|
17
|
-
private wsServer;
|
|
18
16
|
constructor(core: Core, config?: Partial<ServerConfig>);
|
|
19
17
|
private createSession;
|
|
20
18
|
private parseCookies;
|
|
21
19
|
private getClientIP;
|
|
22
|
-
private parseRequestBody;
|
|
23
20
|
private serveStaticFile;
|
|
24
21
|
start(): Promise<void>;
|
|
25
22
|
stop(): void;
|